Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
NodeBranchRenderer.cpp
Go to the documentation of this file.
1/**
2 * @file NodeBranchRenderer.cpp
3 * @brief Implementation of NodeBranchRenderer (Phase 24-REFONTE).
4 * @author Olympe Engine
5 * @date 2026-03-17
6 *
7 * C++14 compliant — no std::optional, structured bindings, std::filesystem.
8 */
9
10#include "NodeBranchRenderer.h"
11
12// ImGui and ImNodes are only available in the full editor build.
13#ifndef OLYMPE_HEADLESS
14# include "../../third_party/imgui/imgui.h"
15# include "../../third_party/imnodes/imnodes.h"
16#endif
17
18namespace Olympe {
19
20
21
22// ============================================================================
23// Constructor
24// ============================================================================
25
32
33// ============================================================================
34// Main render entry point
35// ============================================================================
36
38 const std::unordered_set<int>& connectedAttrIDs)
39{
40#ifndef OLYMPE_HEADLESS
41 // If a preset was changed since the last render, mark the pass as "refresh".
42 const bool refreshing = m_refreshPending;
43 if (refreshing)
44 m_refreshPending = false;
45
46 ImGui::PushID(data.nodeID);
47
48 // ── TITLE BAR (blue, styled via ImNodes) ──────────────────────────────────
49 ImNodes::BeginNodeTitleBar();
50 ImGui::TextUnformatted(data.nodeName.c_str());
51 if (data.breakpoint)
52 {
53 ImGui::SameLine();
54 ImGui::TextColored(ImVec4(1.f, 0.3f, 0.3f, 1.f), "[\xe2\x97\x8f]");
55 }
56 ImNodes::EndNodeTitleBar();
57
58 // ── EXEC PINS (In | Then / Else) ──────────────────────────────────────────
59 // Use columns to align input pins (left) with output pins (right) on the same Y
60 ImGui::Columns(2, "exec_pins", false); // 2 columns, no border
61 ImGui::SetColumnWidth(0, 80.0f);
62
63 // LEFT COLUMN: "In" exec input pin
64 {
65 int inAttrID = data.nodeID * 10000 + 0; // offset 0 = exec-in
66 bool connected = connectedAttrIDs.count(inAttrID) > 0;
67 ImNodes::PushColorStyle(ImNodesCol_Pin, IM_COL32(255, 255, 255, 255)); // White for exec pins
68 ImNodes::BeginInputAttribute(inAttrID, connected ? ImNodesPinShape_TriangleFilled : ImNodesPinShape_Triangle);
69 ImGui::Text("In");
70 ImNodes::EndInputAttribute();
71 ImNodes::PopColorStyle();
72 }
73
74 /* // Obtenir les dimensions du nœud via son ID
75 ImVec2 nodeSize = ImNodes::GetNodeDimensions(data.nodeID);
76 float nodeWidth = nodeSize.x;
77 float nodeHeight = nodeSize.y;
78
79 // Obtenir la position du nœud (espace grille)
80 ImVec2 nodePos = ImNodes::GetNodeGridSpacePos(data.nodeID);
81 float posX = nodePos.x;
82 float posY = nodePos.y;/**/
83
84 // RIGHT COLUMN: "Then" exec output pin
85 ImGui::NextColumn();
86 {
87 int thenAttrID = data.nodeID * 10000 + 100; // offset 100 = exec-out #0
88 bool connected = connectedAttrIDs.count(thenAttrID) > 0;
89 ImNodes::PushColorStyle(ImNodesCol_Pin, IM_COL32(255, 255, 255, 255)); // White for exec pins
90 ImNodes::BeginOutputAttribute(thenAttrID, connected ? ImNodesPinShape_TriangleFilled : ImNodesPinShape_Triangle);
91
92 //// Récupérer la position du pin
93 //ImVec2 pinRectMin = ImGui::GetItemRectMin();
94 //ImVec2 pinRectMax = ImGui::GetItemRectMax();
95 //ImVec2 pinSize = ImGui::GetItemRectSize();
96
97 // Position du pin avant à sa gauche
98 //ImGui::SetCursor(posX);// -nodeWidth - (ImGui::CalcTextSize("True").x + 4.0f));
99 /**/
100 ImGui::Text("True");
101 ImNodes::EndOutputAttribute();
102 ImNodes::PopColorStyle();
103 }
104
105 // LEFT COLUMN: Empty spacer for row alignment
106 ImGui::NextColumn();
107 ImGui::Spacing();
108 ImGui::Spacing();
109
110 // RIGHT COLUMN: "Else" exec output pin
111 ImGui::NextColumn();
112 {
113 int elseAttrID = data.nodeID * 10000 + 101; // offset 101 = exec-out #1
114 bool connected = connectedAttrIDs.count(elseAttrID) > 0;
115 ImNodes::PushColorStyle(ImNodesCol_Pin, IM_COL32(255, 255, 255, 255)); // White for exec pins
116 ImNodes::BeginOutputAttribute(elseAttrID, connected ? ImNodesPinShape_TriangleFilled : ImNodesPinShape_Triangle);
117 ImGui::Text("False");
118 ImNodes::EndOutputAttribute();
119 ImNodes::PopColorStyle();
120 }
121
122 ImGui::Columns(1); // End columns
123
124 // ── CONDITIONS (green, read-only) ──────────────────────────────────────────
125 ImGui::Spacing();
126 //ImGui::Separator();
127 ImGui::Spacing();
129 ImGui::Spacing();
130
131 // Phase 24 FIX: Render data-in pins LAST, below conditions for better UX
132 // ── DYNAMIC DATA PINS (rendered LAST, at bottom) ──
133 if (!data.dynamicPins.empty())
134 {
135 //ImGui::Separator();
136 //ImGui::Spacing();
137 //ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(180.0f / 255.0f, 100.0f / 255.0f, 200.0f / 255.0f, 1.0f)); // Violet header
138 //ImGui::TextUnformatted("=== DATA INPUTS ===");
139 //ImGui::PopStyleColor();
140 ImGui::Spacing();
142 }
143
144 ImGui::PopID();
145#endif
146}
147
148// ============================================================================
149// Section 1 — Title bar (blue background)
150// ============================================================================
151
153{
154#ifndef OLYMPE_HEADLESS
155 // Blue background (#0066CC equivalent) with white text
156 ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.0f, 0.4f, 0.8f, 1.0f));
157 ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.0f, 0.5f, 0.9f, 1.0f));
158 ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.0f, 0.3f, 0.7f, 1.0f));
159 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
160
161 // Use Selectable for proper styling but with no stretch
162 // Note: Pass negative width to auto-fit content, but don't stretch
163 ImGui::TextUnformatted(data.nodeName.c_str());
164 ImGui::PopStyleColor(4);
165
166 if (data.breakpoint)
167 {
168 ImGui::SameLine();
169 ImGui::TextColored(ImVec4(1.f, 0.3f, 0.3f, 1.f), "[\xe2\x97\x8f]");
170 }
171#endif
172}
173
174// ============================================================================
175// Section 2 — Static exec pins (In | Then / Else)
176// ============================================================================
177
179{
180#ifndef OLYMPE_HEADLESS
181 // "In" on the left, "Then" and "Else" on the right.
182 // These pins are STATIC and NEVER editable from the canvas node.
183 const float columnWidth = 150.f;
184
185 ImGui::Text("In");
186 ImGui::SameLine(columnWidth);
187 ImGui::Text("True");
188
189 ImGui::Text(" "); // empty left column
190 ImGui::SameLine(columnWidth);
191 ImGui::Text("False");
192#endif
193}
194
195// ============================================================================
196// Section 3 — Conditions preview (green, read-only, with Pin highlighting)
197// ============================================================================
198
200{
201#ifndef OLYMPE_HEADLESS
202 if (data.conditionRefs.empty())
203 {
204 ImGui::TextDisabled("(no conditions)");
205 return;
206 }
207
208 // Green color for condition preview text
209 const ImVec4 condColor(0.f, 1.f, 0.f, 1.f);
210 // Yellow color for Pin references (highlight)
211 const ImVec4 pinColor(1.0f, 0.843f, 0.0f, 1.0f);
212
213 for (int i = 0; i < static_cast<int>(data.conditionRefs.size()); ++i)
214 {
215 const NodeConditionRef& ref = data.conditionRefs[i];
216
217 ImGui::PushID(i);
218
219 // Logical operator label
220 const char* opLabel = "";
221 if (i == 0)
222 opLabel = " "; // indent first condition (no combinator)
223 else if (ref.logicalOp == LogicalOp::And)
224 opLabel = "And";
225 else
226 opLabel = "Or ";
227
228 // Condition preview with Pin highlighting
229 const ConditionPreset* preset = m_registry.GetPreset(ref.presetID);
230 if (preset)
231 {
232 // Build the condition display with highlighted Pin references
233 ImGui::TextColored(condColor, "%s", opLabel);
234 //ImGui::Text("%s", opLabel);
235 ImGui::SameLine(0.0f, 0.0f);
236
237 // Left operand (GetDisplayString() already includes brackets)
238 //if (preset->left.IsPin())
239 //{
240 // ImGui::TextColored(pinColor, "%s", preset->left.GetDisplayString().c_str());
241 //}
242 //else
243 {
244 ImGui::TextColored(condColor, "%s", preset->left.GetDisplayString().c_str());
245 }
246 ImGui::SameLine(0.0f, 4.0f);
247
248 // Operator
249 std::string opStr;
250 switch (preset->op)
251 {
252 case ComparisonOp::Equal: opStr = "=="; break;
253 case ComparisonOp::NotEqual: opStr = "!="; break;
254 case ComparisonOp::Less: opStr = "<"; break;
255 case ComparisonOp::LessEqual: opStr = "<="; break;
256 case ComparisonOp::Greater: opStr = ">"; break;
257 case ComparisonOp::GreaterEqual: opStr = ">="; break;
258 default: opStr = "?"; break;
259 }
260 ImGui::TextColored(condColor, "%s", opStr.c_str());
261 ImGui::SameLine(0.0f, 4.0f);
262
263 // Right operand (GetDisplayString() already includes brackets)
264 //if (preset->right.IsPin())
265 //{
266 // ImGui::TextColored(pinColor, "%s", preset->right.GetDisplayString().c_str());
267 //}
268 //else
269 {
270 ImGui::TextColored(condColor, "%s", preset->right.GetDisplayString().c_str());
271 }
272
273 // Hover tooltip
274 if (ImGui::IsItemHovered())
275 {
276 ImGui::BeginTooltip();
277 ImGui::Text("Preset: %s", preset->name.c_str());
278 ImGui::Text("ID: %s", preset->id.c_str());
279 ImGui::Text("Expr: %s", preset->GetPreview().c_str());
280 ImGui::EndTooltip();
281 }
282
283 // Click interaction
284 if (ImGui::IsItemClicked())
285 {
289 }
290 }
291 else
292 {
293 ImGui::TextColored(ImVec4(1.f, 0.3f, 0.3f, 1.f),
294 "%s (missing: %s)", opLabel, ref.presetID.c_str());
295 }
296
297 ImGui::PopID();
298 }
299#endif
300}
301
302// ============================================================================
303// Section 4 — Dynamic data pins (rendered only when non-empty)
304// ============================================================================
305
307 const std::unordered_set<int>& connectedAttrIDs)
308{
309#ifndef OLYMPE_HEADLESS
310 // Dynamic data pins are violet (data pin color)
311 const ImVec4 pinColor(180.0f / 255.0f, 100.0f / 255.0f, 200.0f / 255.0f, 1.0f); // Violet
312 const ImU32 pinColorImU32 = IM_COL32(180, 100, 200, 255); // Violet
313
314 // Create a unique ID offset for dynamic pins to avoid conflicts with static pins
315 // Static pins use offsets 0, 100-101. Dynamic pins start at 200.
316 int dynamicPinIDBase = data.nodeID * 10000 + 200;
317
318 // IMPORTANT: Render data-in pins in a way that makes them clearly selectable by ImNodes
319 // Use separate ImGui groups for better hit detection
320 for (size_t i = 0; i < data.dynamicPins.size(); ++i)
321 {
322 const auto& pin = data.dynamicPins[i];
323
324 // Generate unique attribute ID for this pin
325 int attrID = dynamicPinIDBase + static_cast<int>(i);
326 bool connected = connectedAttrIDs.find(attrID) != connectedAttrIDs.end();
327
328 // Register this pin as an input attribute in ImNodes with violet color
329 ImNodes::PushColorStyle(ImNodesCol_Pin, pinColorImU32);
330 ImNodes::BeginInputAttribute(attrID, connected ? ImNodesPinShape_CircleFilled : ImNodesPinShape_Circle);
331
332 // Draw a more prominent violet circle with the label
333 ImGui::TextColored(pinColor, "\xe2\x97\x8f");
334 ImGui::SameLine(0.0f, 4.0f);
335
336 // Use GetShortLabel() ("Pin-in #N") for the slot label
337 const std::string shortLabel = pin.GetShortLabel();
338 ImGui::TextColored(pinColor, "%s", shortLabel.c_str());
339
340 // Hover tooltip: show Pin ID, condition index, operand position, and detail label
341 if (ImGui::IsItemHovered())
342 {
343 const char* posStr = (pin.position == OperandPosition::Left) ? "Left" : "Right";
344 ImGui::BeginTooltip();
345 ImGui::Text("Pin ID: %s", pin.id.c_str());
346 ImGui::Text("Condition index: %d", pin.conditionIndex);
347 ImGui::Text("Operand side: %s", posStr);
348 ImGui::Text("Detail: %s", pin.GetDisplayLabel().c_str());
349 ImGui::EndTooltip();
350 }
351
352 ImNodes::EndInputAttribute();
353 ImNodes::PopColorStyle();
354 }
355#endif
356}
357
358// ============================================================================
359// Pin connector setup (ImNodes integration)
360// ============================================================================
361
363{
364#ifndef OLYMPE_HEADLESS
365 // ImNodes header is included only in full editor builds.
366 // This method is intentionally left as a no-op here; the ImNodes integration
367 // is wired by the host application which controls the ImNodes frame.
368 // The yellow TextColored labels are rendered by RenderDynamicPinsSection.
369 (void)data;
370#endif
371}
372
373// ============================================================================
374// Pin regeneration (Modal-to-Canvas workflow)
375// ============================================================================
376
378 std::vector<NodeConditionRef>& conditionRefs)
379{
381}
382
383// ============================================================================
384// Preset change notification
385// ============================================================================
386
387void NodeBranchRenderer::NotifyPresetChanged(const std::string& /*changedPresetID*/)
388{
389 m_refreshPending = true;
390}
391
392// ============================================================================
393// Private helpers
394// ============================================================================
395
397{
398#ifndef OLYMPE_HEADLESS
399 ImGui::TextDisabled("── %s ──", label);
400#endif
401}
402
404{
405#ifndef OLYMPE_HEADLESS
406 ImGui::Separator();
407 ImGui::Spacing();
408#endif
409}
410
411} // namespace Olympe
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
ImGui renderer for NodeBranch nodes with 4 distinct sections (Phase 24-REFONTE).
Manages the global pool of ConditionPreset objects.
ConditionPreset * GetPreset(const std::string &id)
Returns a mutable pointer to the preset, or nullptr if not found.
Generates, tracks, and invalidates DynamicDataPin objects for a node.
void RegeneratePinsFromConditions(std::vector< NodeConditionRef > &conditionRefs, const std::vector< ConditionRef > &operandRefs=std::vector< ConditionRef >())
Regenerates pins from the current condition list.
DynamicDataPinManager & m_pinManager
Shared dynamic pin manager.
void NotifyPresetChanged(const std::string &changedPresetID)
Notifies the renderer that a preset has been updated.
void SetupDynamicPinConnectors(const NodeBranchData &data)
Sets up ImNodes connectors for each dynamic pin.
void RenderConditionsSection(const NodeBranchData &data)
Renders Section 3: read-only conditions preview (green text).
void RenderTitleSection(const NodeBranchData &data)
Renders Section 1: title bar with blue background.
void RenderExecPinsSection(const NodeBranchData &data)
Renders Section 2: static exec pins (In / Then / Else).
NodeBranchRenderer(ConditionPresetRegistry &registry, DynamicDataPinManager &pinManager)
Constructs the renderer with its dependencies.
int m_lastClickedCondition
Interaction state.
void RenderSectionHeader(const char *label)
std::function< void(int)> OnConditionClicked
Fired when the user clicks on a condition row.
void RenderDynamicPinsSection(const NodeBranchData &data, const std::unordered_set< int > &connectedAttrIDs={})
Renders Section 4: dynamic data pins (yellow, conditional).
ConditionPresetRegistry & m_registry
Shared global registry.
void TriggerPinRegeneration(std::vector< NodeConditionRef > &conditionRefs)
Regenerates dynamic data pins from an updated condition list.
void RenderNode(const NodeBranchData &data, const std::unordered_set< int > &connectedAttrIDs={})
Renders the full NodeBranch node in the current ImNodes context.
< Provides AssetID and INVALID_ASSET_ID
@ And
Combined with AND.
@ Left
Pin provides data for the left operand of the condition.
A globally-stored, reusable condition expression.
Lightweight snapshot of a NodeBranch required for rendering.
std::string nodeName
Display name (e.g. "Branch #3")
std::vector< DynamicDataPin > dynamicPins
Generated dynamic data pins.
std::vector< NodeConditionRef > conditionRefs
Ordered condition references.
bool breakpoint
Whether a breakpoint is set.
int nodeID
Numeric node identifier (for ImNodes attribute UIDs)
One entry in a NodeBranch's conditions list.