Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
Clipboard.cpp
Go to the documentation of this file.
1/**
2 * @file Clipboard.cpp
3 * @brief Implementation of NodeGraphClipboard.
4 * @author Olympe Engine
5 * @date 2026-02-24
6 *
7 * @details
8 * Serialisation format (JSON, one object per copied node):
9 * {
10 * "nodes": [
11 * {
12 * "type": "BT_Action",
13 * "name": "Move",
14 * "actionType": "MoveToLocation",
15 * "conditionType": "",
16 * "decoratorType": "",
17 * "params": { "speed": "5.0" },
18 * "dx": 0.0,
19 * "dy": 0.0
20 * }
21 * ]
22 * }
23 *
24 * "dx"/"dy" are position offsets relative to the top-left bounding corner of
25 * the copied selection, so pasted nodes reproduce the original spatial layout.
26 *
27 * C++14 compliant - no C++17/20 features.
28 */
29
30#include "Clipboard.h"
31#include "../system/system_utils.h"
32#include "../third_party/imgui/imgui.h"
33#include "../third_party/imnodes/imnodes.h"
34#include "../third_party/nlohmann/json.hpp"
35#include <string>
36#include <vector>
37#include <cstring>
38#include <cfloat>
39#include <algorithm>
40
42
43namespace Olympe
44{
45
46// ============================================================================
47// Constants
48// ============================================================================
49
50const char* NodeGraphClipboard::k_ClipPrefix = "OLYMPE_NG_CLIP:";
51
52// Multiplier used to convert local node IDs to ImNodes global UIDs.
53// Must match the value in NodeGraphPanel.cpp.
54static constexpr int CLIP_GRAPH_ID_MULTIPLIER = 10000;
55
56// ============================================================================
57// Singleton
58// ============================================================================
59
61{
62 static NodeGraphClipboard s_instance;
63 return s_instance;
64}
65
66// ============================================================================
67// CopySelectedNodes
68// ============================================================================
69
71{
72 if (graph == nullptr)
73 return;
74
75 int numSelected = ImNodes::NumSelectedNodes();
76 if (numSelected <= 0)
77 return;
78
79 std::vector<int> selectedUIDs(numSelected);
80 ImNodes::GetSelectedNodes(selectedUIDs.data());
81
82 // Collect local node IDs and find bounding box for relative offsets.
83 float minX = FLT_MAX;
84 float minY = FLT_MAX;
85
86 std::vector<GraphNode*> selectedNodes;
87 selectedNodes.reserve(static_cast<size_t>(numSelected));
88
89 for (int globalUID : selectedUIDs)
90 {
91 int localId = globalUID - (graphID * CLIP_GRAPH_ID_MULTIPLIER);
92 GraphNode* node = graph->GetNode(localId);
93 if (node == nullptr)
94 continue;
95
96 selectedNodes.push_back(node);
97 if (node->posX < minX) minX = node->posX;
98 if (node->posY < minY) minY = node->posY;
99 }
100
101 if (selectedNodes.empty())
102 return;
103
104 // Build JSON payload.
105 json payload = json::object();
106 json nodesArr = json::array();
107
108 for (GraphNode* node : selectedNodes)
109 {
110 json nodeObj = json::object();
111 nodeObj["type"] = NodeTypeToString(node->type);
112 nodeObj["name"] = node->name;
113 nodeObj["actionType"] = node->actionType;
114 nodeObj["conditionType"] = node->conditionType;
115 nodeObj["decoratorType"] = node->decoratorType;
116 nodeObj["dx"] = node->posX - minX;
117 nodeObj["dy"] = node->posY - minY;
118
119 json paramsObj = json::object();
120 for (auto it = node->parameters.begin(); it != node->parameters.end(); ++it)
121 {
122 paramsObj[it->first] = it->second;
123 }
124 nodeObj["params"] = paramsObj;
125
126 nodesArr.push_back(nodeObj);
127 }
128
129 payload["nodes"] = nodesArr;
130
131 // Write to system clipboard with prefix.
132 std::string clipStr = k_ClipPrefix;
133 clipStr += payload.dump();
134 ImGui::SetClipboardText(clipStr.c_str());
135
136 SYSTEM_LOG << "[NodeGraphClipboard] Copied " << numSelected << " node(s) to clipboard.\n";
137}
138
139// ============================================================================
140// PasteNodes
141// ============================================================================
142
144 float mousePosX, float mousePosY,
145 bool snapToGrid, float snapGridSize)
146{
147 if (graph == nullptr)
148 return;
149
150 const char* rawClip = ImGui::GetClipboardText();
151 if (rawClip == nullptr)
152 return;
153
154 // Validate prefix.
155 size_t prefixLen = std::strlen(k_ClipPrefix);
156 if (std::strncmp(rawClip, k_ClipPrefix, prefixLen) != 0)
157 return;
158
159 const char* jsonStart = rawClip + prefixLen;
160
162 try
163 {
164 payload = json::parse(jsonStart);
165 }
166 catch (...)
167 {
168 SYSTEM_LOG << "[NodeGraphClipboard] Paste failed: invalid JSON payload.\n";
169 return;
170 }
171
172 if (!payload.is_object() || !payload.contains("nodes") || !payload["nodes"].is_array())
173 {
174 SYSTEM_LOG << "[NodeGraphClipboard] Paste failed: missing 'nodes' array.\n";
175 return;
176 }
177
178 const json& nodesArr = payload["nodes"];
179 int pastedCount = 0;
180
181 for (size_t i = 0; i < nodesArr.size(); ++i)
182 {
183 const json& nodeObj = nodesArr[i];
184 if (!nodeObj.is_object())
185 continue;
186
187 std::string typeStr;
188 std::string nodeName;
189 std::string actionType;
190 std::string conditionType;
191 std::string decoratorType;
192 float dx = 0.0f;
193 float dy = 0.0f;
194
195 if (nodeObj.contains("type") && nodeObj["type"].is_string())
196 typeStr = nodeObj["type"].get<std::string>();
197 if (nodeObj.contains("name") && nodeObj["name"].is_string())
198 nodeName = nodeObj["name"].get<std::string>();
199 if (nodeObj.contains("actionType") && nodeObj["actionType"].is_string())
200 actionType = nodeObj["actionType"].get<std::string>();
201 if (nodeObj.contains("conditionType") && nodeObj["conditionType"].is_string())
202 conditionType = nodeObj["conditionType"].get<std::string>();
203 if (nodeObj.contains("decoratorType") && nodeObj["decoratorType"].is_string())
204 decoratorType = nodeObj["decoratorType"].get<std::string>();
205 if (nodeObj.contains("dx") && nodeObj["dx"].is_number())
206 dx = nodeObj["dx"].get<float>();
207 if (nodeObj.contains("dy") && nodeObj["dy"].is_number())
208 dy = nodeObj["dy"].get<float>();
209
211 float pasteX = mousePosX + dx;
212 float pasteY = mousePosY + dy;
213
214 // Apply snap-to-grid to each pasted node individually.
215 if (snapToGrid && snapGridSize > 0.0f)
216 {
217 pasteX = std::roundf(pasteX / snapGridSize) * snapGridSize;
218 pasteY = std::roundf(pasteY / snapGridSize) * snapGridSize;
219 }
220
221 int newId = graph->CreateNode(ntype, pasteX, pasteY, nodeName.empty() ? typeStr : nodeName);
222 GraphNode* newNode = graph->GetNode(newId);
223 if (newNode == nullptr)
224 continue;
225
226 // Apply immediate ImNodes position so the node appears at the correct
227 // grid location this frame (before the next RenderGraph pass).
228 int globalUID = graphID * CLIP_GRAPH_ID_MULTIPLIER + newId;
229 ImNodes::SetNodeGridSpacePos(globalUID, ImVec2(pasteX, pasteY));
230
231 newNode->actionType = actionType;
232 newNode->conditionType = conditionType;
233 newNode->decoratorType = decoratorType;
234
235 // Restore parameters.
236 if (nodeObj.contains("params") && nodeObj["params"].is_object())
237 {
238 const json& paramsObj = nodeObj["params"];
239 for (auto it = paramsObj.begin(); it != paramsObj.end(); ++it)
240 {
241 if (it.value().is_string())
242 newNode->parameters[it.key()] = it.value().get<std::string>();
243 }
244 }
245
246 ++pastedCount;
247 }
248
249 if (pastedCount > 0)
250 {
251 graph->MarkDirty();
252 SYSTEM_LOG << "[NodeGraphClipboard] Pasted " << pastedCount << " node(s).\n";
253 }
254}
255
256} // namespace Olympe
nlohmann::json json
Node-graph clipboard: copy/paste selected nodes via ImGui system clipboard.
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
Singleton clipboard for node-graph copy / paste operations.
Definition Clipboard.h:42
void PasteNodes(NodeGraph *graph, int graphID, float mousePosX, float mousePosY, bool snapToGrid=false, float snapGridSize=16.0f)
Read the system clipboard, deserialise nodes and create them in the active graph under the current mo...
static NodeGraphClipboard & Get()
Returns the singleton instance.
Definition Clipboard.cpp:60
void CopySelectedNodes(NodeGraph *graph, int graphID)
Serialise currently selected nodes to JSON and write to the system clipboard.
Definition Clipboard.cpp:70
static const char * k_ClipPrefix
Prefix that marks Olympe node-graph clipboard payloads.
Definition Clipboard.h:80
< Provides AssetID and INVALID_ASSET_ID
nlohmann::json json
const char * NodeTypeToString(NodeType type)
static constexpr int CLIP_GRAPH_ID_MULTIPLIER
Definition Clipboard.cpp:54
NodeType StringToNodeType(const std::string &str)
nlohmann::json json
#define SYSTEM_LOG