Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
NodeBranchRenderer.h
Go to the documentation of this file.
1/**
2 * @file NodeBranchRenderer.h
3 * @brief ImGui renderer for NodeBranch nodes with 4 distinct sections (Phase 24-REFONTE).
4 * @author Olympe Engine
5 * @date 2026-03-17
6 *
7 * @details
8 * NodeBranchRenderer draws a single NodeBranch inside the ImGui / ImNodes
9 * graph canvas. The node is divided into four visual sections separated by
10 * horizontal dividers, matching the validated mockup:
11 *
12 * @code
13 * ┌─────────────────────────────────────────────────┐
14 * │ Section 1 — Title bar (blue background) │
15 * │ Is Health Critical? │
16 * ├─────────────────────────────────────────────────┤
17 * │ Section 2 — Exec pins (static, never editable) │
18 * │ >> In Then >> │
19 * │ Else >> │
20 * ├─────────────────────────────────────────────────┤
21 * │ Section 3 — Conditions preview (READ-ONLY) │
22 * │ [mHealth] <= [2] (green, monospace) │
23 * │ And [mSpeed] <= [100.00] │
24 * │ Or [mSpeed] == [Pin : 1] │
25 * ├─────────────────────────────────────────────────┤
26 * │ Section 4 — Dynamic data pins (yellow, cond.) │
27 * │ * In #1: [mSpeed] == [Pin : 1] │
28 * └─────────────────────────────────────────────────┘
29 * @endcode
30 *
31 * Interaction:
32 * - Hover over a condition row -> tooltip showing preset details.
33 * - Click a condition row -> fires OnConditionClicked(conditionIndex).
34 * - Dynamic pins support drag/connect via ImNodes.
35 * - Section 4 is only rendered when at least one dynamic pin exists.
36 *
37 * Auto-update:
38 * Call NotifyPresetChanged(presetID) whenever a preset is updated so that
39 * the cached preview strings are refreshed.
40 *
41 * C++14 compliant — no std::optional, structured bindings, std::filesystem.
42 */
43
44#pragma once
45
46#include <string>
47#include <vector>
48#include <functional>
49#include <unordered_set>
50
51#include "../ConditionPreset/ConditionPreset.h"
52#include "../ConditionPreset/ConditionPresetRegistry.h"
53#include "../ConditionPreset/NodeConditionRef.h"
54#include "../ConditionPreset/DynamicDataPin.h"
55#include "../ConditionPreset/DynamicDataPinManager.h"
56
57namespace Olympe {
58
59/**
60 * @struct NodeBranchData
61 * @brief Lightweight snapshot of a NodeBranch required for rendering.
62 *
63 * @details
64 * The renderer does not own the node; it receives a NodeBranchData reference
65 * each frame so it remains stateless with respect to node ownership.
66 */
68 int nodeID; ///< Numeric node identifier (for ImNodes attribute UIDs)
69 std::string nodeName; ///< Display name (e.g. "Branch #3")
70 std::vector<NodeConditionRef> conditionRefs; ///< Ordered condition references
71 std::vector<DynamicDataPin> dynamicPins; ///< Generated dynamic data pins
72 bool breakpoint = false; ///< Whether a breakpoint is set
73};
74
75/**
76 * @class NodeBranchRenderer
77 * @brief Renders a NodeBranch using ImGui/ImNodes with 4 sections.
78 *
79 * @details
80 * The renderer is stateless with respect to node data: it reads from the
81 * NodeBranchData passed each frame. Internal state is limited to hover/click
82 * tracking for interaction.
83 *
84 * Usage:
85 * @code
86 * NodeBranchRenderer renderer(registry, pinManager);
87 * // In the ImNodes frame:
88 * renderer.RenderNode(nodeID, branchData);
89 * @endcode
90 */
92public:
93
94 /**
95 * @brief Constructs the renderer with its dependencies.
96 * @param registry Global ConditionPresetRegistry.
97 * @param pinManager DynamicDataPinManager for the node being rendered.
98 */
101
103
104 // Non-copyable
107
108 // -----------------------------------------------------------------------
109 // Main render entry point
110 // -----------------------------------------------------------------------
111
112 /**
113 * @brief Renders the full NodeBranch node in the current ImNodes context.
114 *
115 * Must be called between ImNodes::BeginNode() and ImNodes::EndNode().
116 * In headless builds this is a no-op.
117 *
118 * @param data Current snapshot of the node's data.
119 * @param connectedAttrIDs Set of attribute IDs that are currently connected.
120 * Pins in this set are rendered filled; others outlined.
121 */
122 void RenderNode(const NodeBranchData& data,
123 const std::unordered_set<int>& connectedAttrIDs = {});
124
125 // -----------------------------------------------------------------------
126 // Section renderers (also exposed for unit testing / partial renders)
127 // -----------------------------------------------------------------------
128
129 /**
130 * @brief Renders Section 1: title bar with blue background.
131 *
132 * Displays the node name on a blue-highlighted background row.
133 * @param data Node data.
134 */
135 void RenderTitleSection(const NodeBranchData& data);
136
137 /**
138 * @brief Renders Section 2: static exec pins (In / Then / Else).
139 *
140 * Shows "In" on the left and "Then" / "Else" on the right.
141 * These pins are NEVER editable from this section.
142 * @param data Node data.
143 */
144 void RenderExecPinsSection(const NodeBranchData& data);
145
146 /**
147 * @brief Renders Section 3: read-only conditions preview (green text).
148 *
149 * Each condition is displayed in the format:
150 * " [mHealth] <= [2]"
151 * "And [mSpeed] <= [100.00]"
152 * Hovering shows a tooltip; clicking fires OnConditionClicked.
153 * @param data Node data.
154 */
155 void RenderConditionsSection(const NodeBranchData& data);
156
157 /**
158 * @brief Renders Section 4: dynamic data pins (yellow, conditional).
159 *
160 * Only rendered when data.dynamicPins is non-empty.
161 * Format: "In #<idx>[L/R]: <condPreview>"
162 * @param data Node data.
163 * @param connectedAttrIDs Set of connected attribute IDs (for filled/outlined shape).
164 */
166 const std::unordered_set<int>& connectedAttrIDs = {});
167
168 // -----------------------------------------------------------------------
169 // ImNodes connector setup
170 // -----------------------------------------------------------------------
171
172 /**
173 * @brief Sets up ImNodes connectors for each dynamic pin.
174 *
175 * Call this within an ImNodes::BeginNode() / ImNodes::EndNode() block so
176 * each yellow dynamic pin becomes drag-connectable in the graph editor.
177 * Each pin receives a connector ID derived from pin.id.
178 *
179 * In headless / test builds this is a no-op.
180 *
181 * @param data Node data (dynamicPins are iterated).
182 */
183 void SetupDynamicPinConnectors(const NodeBranchData& data);
184
185 // -----------------------------------------------------------------------
186 // Pin regeneration (Modal-to-Canvas workflow)
187 // -----------------------------------------------------------------------
188
189 /**
190 * @brief Regenerates dynamic data pins from an updated condition list.
191 *
192 * Call this after the user confirms changes in the edit modal so the
193 * canvas reflects the new pin set on the next render frame.
194 *
195 * Delegates to DynamicDataPinManager::RegeneratePinsFromConditions() so
196 * the host does not need a direct reference to the pin manager.
197 *
198 * @param conditionRefs Node's condition list (modified in-place to store
199 * updated pin IDs).
200 */
201 void TriggerPinRegeneration(std::vector<NodeConditionRef>& conditionRefs);
202
203 // -----------------------------------------------------------------------
204 // Interaction state
205 // -----------------------------------------------------------------------
206
207 /**
208 * @brief Returns the index of the last condition row that was clicked, or
209 * -1 if none.
210 */
212
213 /**
214 * @brief Resets the last-clicked condition index to -1.
215 */
217
218 // -----------------------------------------------------------------------
219 // Callbacks
220 // -----------------------------------------------------------------------
221
222 /**
223 * @brief Fired when the user clicks on a condition row.
224 * Arg: zero-based condition index.
225 */
226 std::function<void(int)> OnConditionClicked;
227
228 // -----------------------------------------------------------------------
229 // Preset change notification
230 // -----------------------------------------------------------------------
231
232 /**
233 * @brief Notifies the renderer that a preset has been updated.
234 *
235 * Marks the node as needing a label refresh. The next call to RenderNode()
236 * will recompute preview strings from the registry.
237 *
238 * @param changedPresetID UUID of the modified preset.
239 */
240 void NotifyPresetChanged(const std::string& changedPresetID);
241
242 // -----------------------------------------------------------------------
243 // Testable state helpers
244 // -----------------------------------------------------------------------
245
246 /**
247 * @brief Returns true if any preset change notification is pending (dirty).
248 */
249 bool IsRefreshPending() const { return m_refreshPending; }
250
251 /**
252 * @brief Clears the refresh-pending flag (called after refresh is complete).
253 */
255
256private:
257
258 // -----------------------------------------------------------------------
259 // ImGui color constants (matching mockup spec)
260 // -----------------------------------------------------------------------
261
262 /// @brief Blue title bar background color (#0066CC equivalent).
263 static constexpr float kTitleR = 0.0f;
264 static constexpr float kTitleG = 0.4f;
265 static constexpr float kTitleB = 0.8f;
266
267 /// @brief Yellow dynamic-pin label color (#FFD700 equivalent).
268 static constexpr float kDynPinR = 1.0f;
269 static constexpr float kDynPinG = 0.843f;
270 static constexpr float kDynPinB = 0.0f;
271
272 // -----------------------------------------------------------------------
273 // Section helpers
274 // -----------------------------------------------------------------------
275
276 void RenderSectionHeader(const char* label);
278
279 // -----------------------------------------------------------------------
280 // State
281 // -----------------------------------------------------------------------
282
283 ConditionPresetRegistry& m_registry; ///< Shared global registry
284 DynamicDataPinManager& m_pinManager; ///< Shared dynamic pin manager
285 int m_lastClickedCondition = -1; ///< Interaction state
286 bool m_refreshPending = false; ///< Dirty flag
287};
288
289} // namespace Olympe
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
Manages the global pool of ConditionPreset objects.
Generates, tracks, and invalidates DynamicDataPin objects for a node.
Renders a NodeBranch using ImGui/ImNodes with 4 sections.
static constexpr float kTitleB
static constexpr float kDynPinG
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.
static constexpr float kDynPinB
void RenderConditionsSection(const NodeBranchData &data)
Renders Section 3: read-only conditions preview (green text).
int GetLastClickedConditionIndex() const
Returns the index of the last condition row that was clicked, or -1 if none.
void RenderTitleSection(const NodeBranchData &data)
Renders Section 1: title bar with blue background.
static constexpr float kTitleR
Blue title bar background color (#0066CC equivalent).
void ClearRefreshPending()
Clears the refresh-pending flag (called after refresh is complete).
void RenderExecPinsSection(const NodeBranchData &data)
Renders Section 2: static exec pins (In / Then / Else).
static constexpr float kDynPinR
Yellow dynamic-pin label color (#FFD700 equivalent).
NodeBranchRenderer(const NodeBranchRenderer &)=delete
int m_lastClickedCondition
Interaction state.
NodeBranchRenderer & operator=(const NodeBranchRenderer &)=delete
void RenderSectionHeader(const char *label)
void ClearLastClickedCondition()
Resets the last-clicked condition index to -1.
bool IsRefreshPending() const
Returns true if any preset change notification is pending (dirty).
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).
static constexpr float kTitleG
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
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)