Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
VisualScriptEditorPanel_Utilities.cpp
Go to the documentation of this file.
1/**
2 * @file VisualScriptEditorPanel_Utilities.cpp
3 * @brief Utility helper methods for VisualScriptEditorPanel (Phase 5 extraction).
4 * @author Olympe Engine
5 * @date 2026-03-09
6 *
7 * @details
8 * This file contains 6 utility helper methods extracted from VisualScriptEditorPanel.cpp
9 * for improved code organization and maintainability:
10 *
11 * 1. ValidateAndCleanBlackboardEntries() — Remove invalid blackboard entries (BUG-002 Fix #1)
12 * 2. CommitPendingBlackboardEdits() — Flush deferred blackboard key-name edits
13 * 3. ResetViewportBeforeSave() — Save and reset ImNodes viewport panning (BUG-003 Fix)
14 * 4. AfterSave() — Restore ImNodes viewport panning after save (BUG-003 Fix #5)
15 * 5. ScreenToCanvasPos() — Convert screen-space to ImNodes editor-space coordinates
16 * 6. GetVariablesByType() — Type-filtered variable utility (UX Enhancement #3)
17 *
18 * Phase 24 Refactoring: Extract utility helpers to separate compilation unit for
19 * cleaner separation of concerns and faster iteration during UI development.
20 */
21
23#include "../system/system_utils.h"
24#include "../system/system_consts.h"
25#include "../third_party/imgui/imgui.h"
26#include "../third_party/imnodes/imnodes.h"
27
28#include <algorithm>
29#include <iostream>
30
31namespace Olympe {
32
33// ============================================================================
34// Blackboard validation helpers (BUG-002 Fix #1)
35// ============================================================================
36
37/**
38 * @brief ValidateAndCleanBlackboardEntries
39 *
40 * Remove invalid blackboard entries from the template's blackboard list.
41 * Entries with empty keys or VariableType::None are considered invalid and
42 * are filtered out to prevent serialization crashes and UI inconsistencies.
43 *
44 * Invalid entries can occur when:
45 * - User creates a variable entry but doesn't specify a name
46 * - Type deserialization failed due to malformed JSON
47 * - Legacy data migration left orphaned entries
48 *
49 * This method is called before Save() and SaveAs() to ensure a clean state.
50 * Removed entries are logged for debugging; m_dirty is set to track changes.
51 */
53{
54 std::vector<BlackboardEntry>& entries = m_template.Blackboard;
55 size_t before = entries.size();
56
57 entries.erase(
58 std::remove_if(entries.begin(), entries.end(),
59 [](const BlackboardEntry& e) {
60 if (e.Key.empty()) {
61 SYSTEM_LOG << "[VSEditor] ValidateAndClean: removing entry with empty key\n";
62 return true;
63 }
64 if (e.Type == VariableType::None) {
65 SYSTEM_LOG << "[VSEditor] ValidateAndClean: removing entry '"
66 << e.Key << "' with VariableType::None\n";
67 return true;
68 }
69 return false;
70 }),
71 entries.end());
72
73 size_t removed = before - entries.size();
74 if (removed > 0)
75 {
76 SYSTEM_LOG << "[VSEditor] ValidateAndClean: removed " << removed
77 << " invalid blackboard entries\n";
78 m_dirty = true;
79 }
80}
81
82/**
83 * @brief CommitPendingBlackboardEdits
84 *
85 * Flush any deferred blackboard entry key-name edits to the template.
86 * When the user is editing a variable key name in the UI, the change is
87 * stored in m_pendingBlackboardEdits (a map of index -> new key name) to
88 * defer costly operations until the edit is complete or save is triggered.
89 *
90 * This method is called before serialization (Save/SaveAs) to ensure all
91 * user edits are persisted. After flushing, m_pendingBlackboardEdits is cleared.
92 *
93 * Bounds checking is performed to prevent out-of-range access.
94 */
95void VisualScriptEditorPanel::CommitPendingBlackboardEdits()
96{
97 for (std::unordered_map<int, std::string>::iterator it = m_pendingBlackboardEdits.begin();
98 it != m_pendingBlackboardEdits.end(); ++it)
99 {
100 int idx = it->first;
101 if (idx >= 0 && idx < static_cast<int>(m_template.Blackboard.size()))
102 {
103 m_template.Blackboard[static_cast<size_t>(idx)].Key = it->second;
104 }
105 }
106 m_pendingBlackboardEdits.clear();
107}
108
109// ============================================================================
110// BUG-003 Viewport helpers
111// ============================================================================
112
113/**
114 * @brief ResetViewportBeforeSave
115 *
116 * Save the current ImNodes viewport panning state and reset panning to (0, 0).
117 * This is part of BUG-003 fix to prevent viewport jump when saving.
118 *
119 * Rationale:
120 * 1. SyncNodePositionsFromImNodes() is called during Save() to capture
121 * the current canvas state before serialization.
122 * 2. GetNodeGridSpacePos() is pan-independent, but stale ImNodes internal
123 * state could corrupt the capture if the viewport offset is non-zero.
124 * 3. By resetting panning to (0, 0) before the capture, we ensure a
125 * clean state for position serialization.
126 * 4. AfterSave() restores the original panning so the user's viewport
127 * position is preserved (canvas doesn't visually jump).
128 *
129 * This is called at the very beginning of Save() / SaveAs().
130 */
131void VisualScriptEditorPanel::ResetViewportBeforeSave()
132{
133 SYSTEM_LOG << "[VSEditor] ResetViewportBeforeSave: saving current panning\n";
134 m_lastViewportPanning = Vector::FromImVec2(ImNodes::EditorContextGetPanning());
135 m_viewportResetDone = true;
136
137 // Reset panning to (0, 0) so that any residual editor-space offset from
138 // user navigation is zeroed out before SyncNodePositionsFromImNodes reads
139 // GetNodeGridSpacePos (which is already pan-independent, but this ensures
140 // no subtle ImNodes internal state leaks into the saved positions).
141 ImNodes::EditorContextResetPanning(ImVec2(0.0f, 0.0f));
142 SYSTEM_LOG << "[VSEditor] ResetViewportBeforeSave: panning reset to (0,0) "
143 << "(was " << m_lastViewportPanning.x << "," << m_lastViewportPanning.y << ")\n";
144}
145
146/**
147 * @brief AfterSave
148 *
149 * Restore the ImNodes viewport panning to its pre-save state.
150 * This completes BUG-003 fix #5: viewport restoration.
151 *
152 * Called at the end of Save() / SaveAs() after serialization completes.
153 * Without this restoration, the user would see the canvas visually jump
154 * to (0, 0) panning every time they save.
155 *
156 * If ResetViewportBeforeSave() was not called (or Save cancelled), this
157 * is a no-op (guarded by m_viewportResetDone flag).
158 */
159void VisualScriptEditorPanel::AfterSave()
160{
161 if (!m_viewportResetDone)
162 return;
163
164 // Restore the viewport so the canvas does not visually jump for the user.
165 ImNodes::EditorContextResetPanning(m_lastViewportPanning.ToImVec2());
166 m_viewportResetDone = false;
167 SYSTEM_LOG << "[VSEditor] AfterSave: viewport panning restored to ("
168 << m_lastViewportPanning.x << "," << m_lastViewportPanning.y << ")\n";
169}
170
171/**
172 * @brief ScreenToCanvasPos
173 *
174 * Convert absolute screen-space coordinates to ImNodes editor (canvas) space.
175 * Used for context menu positioning, mouse hit testing, and UI interactions.
176 *
177 * The transformation is:
178 * canvasX = screenX - windowPos.x - panning.x
179 * canvasY = screenY - windowPos.y - panning.y
180 *
181 * where:
182 * - screenPos: absolute screen coordinate (e.g., from ImGui::GetMousePos())
183 * - windowPos: top-left corner of the ImGui window containing the canvas
184 * - panning: current ImNodes viewport pan offset
185 * - zoom: implicit 1.0f (ImNodes 0.4 does not expose zoom API)
186 *
187 * @param screenPos Absolute screen-space coordinate
188 * @return ImVec2 Editor-space coordinate for hit testing or node placement
189 */
190ImVec2 VisualScriptEditorPanel::ScreenToCanvasPos(ImVec2 screenPos) const
191{
192 // Convert absolute screen-space position to ImNodes editor (canvas) space.
193 // Editor space = grid space + panning, so:
194 // editorX = screenX - canvasOrigin.x - windowPos.x
195 // ImNodes 0.4 has no zoom API; zoom is implicitly 1.0f.
196 ImVec2 canvasPanning = ImNodes::EditorContextGetPanning();
197 ImVec2 windowPos = ImGui::GetWindowPos();
198 return ImVec2(
201}
202
203// ============================================================================
204// UX Enhancement #3 — Type-filtered variable utility
205// ============================================================================
206
207/**
208 * @brief GetVariablesByType
209 *
210 * Static helper method to filter blackboard entries by variable type.
211 * Returns a new vector containing only entries whose Type matches expectedType.
212 *
213 * This is used in UI panels to populate variable dropdowns with only
214 * compatible types. For example, when connecting a data pin to a variable,
215 * only variables of the matching type are shown in the dropdown.
216 *
217 * Example usage:
218 * std::vector<BlackboardEntry> intVars = GetVariablesByType(
219 * m_template.Blackboard, VariableType::Int);
220 *
221 * @param allVars Input list of all blackboard entries
222 * @param expectedType Desired variable type to filter by
223 * @return std::vector<BlackboardEntry> Filtered list (copies, not references)
224 */
225/*static*/
226std::vector<BlackboardEntry> VisualScriptEditorPanel::GetVariablesByType(
227 const std::vector<BlackboardEntry>& allVars,
228 VariableType expectedType)
229{
230 std::vector<BlackboardEntry> filtered;
231 for (size_t i = 0; i < allVars.size(); ++i)
232 {
233 if (allVars[i].Type == expectedType)
234 filtered.push_back(allVars[i]);
235 }
236 return filtered;
237}
238
239// ============================================================================
240// Phase 2 Blackboard Validation Helpers
241// ============================================================================
242
243/**
244 * @brief ValidateBlackboardKey
245 *
246 * Validates a blackboard entry key according to the following rules:
247 * 1. Key must not be empty
248 * 2. Key must not be a duplicate (checked against all other entries)
249 * 3. Global keys should follow "scope:key" format (warning, not error)
250 *
251 * @param key The key string to validate
252 * @param isGlobal Whether this is a global variable
253 * @param excludeIndex Skip duplicate check for entry at this index (-1 = check all)
254 * @return Validation result with error/warning messages
255 */
256BlackboardValidationResult VisualScriptEditorPanel::ValidateBlackboardKey(
257 const std::string& key,
258 bool isGlobal,
259 int excludeIndex)
260{
262 result.IsValid = true;
263
264 // Rule 1: Key must not be empty
265 if (key.empty())
266 {
267 result.IsValid = false;
268 result.ErrorMessage = "Key cannot be empty";
269 return result;
270 }
271
272 // Rule 2: Check for duplicates
273 for (size_t i = 0; i < m_template.Blackboard.size(); ++i)
274 {
275 if (static_cast<int>(i) == excludeIndex)
276 continue; // Skip the entry we're editing
277
278 if (m_template.Blackboard[i].Key == key)
279 {
280 result.IsValid = false;
281 result.ErrorMessage = "Key already exists: " + key;
282 return result;
283 }
284 }
285
286 // Rule 3: Global keys should follow "scope:key" format
287 if (isGlobal && key.find(':') == std::string::npos)
288 {
289 result.IsValid = true; // Still valid, but warn
290 result.WarningMessage = "Global key should follow 'scope:key' format";
291 }
292
293 return result;
294}
295
296/**
297 * @brief ValidateBlackboardEntry
298 *
299 * Validates a complete blackboard entry for consistency.
300 * Checks that the entry has both a key and a valid type.
301 *
302 * @param entry The entry to validate
303 * @return true if entry is valid (non-empty key, Type != None)
304 */
305bool VisualScriptEditorPanel::ValidateBlackboardEntry(const BlackboardEntry& entry)
306{
307 if (entry.Key.empty())
308 return false;
309 if (entry.Type == VariableType::None)
310 return false;
311 return true;
312}
313
314} // namespace Olympe
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
ImNodes-based graph editor for ATS Visual Script graphs (Phase 5).
std::vector< BlackboardEntry > Blackboard
Local blackboard declared in this graph.
TaskGraphTemplate m_template
The template currently being edited.
void ValidateAndCleanBlackboardEntries()
Removes blackboard entries with empty keys or VariableType::None.
static Vector FromImVec2(const ImVec2 &v)
Definition vector.h:66
< Provides AssetID and INVALID_ASSET_ID
@ None
Uninitialized / empty value.
Single entry in the graph's declared blackboard schema (local or global).
Result of blackboard key validation (Phase 26).
std::string WarningMessage
Set if validation succeeded but has warnings.
std::string ErrorMessage
Set if validation failed.
#define SYSTEM_LOG