Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
VisualScriptEditorPanel_RenderingCore.cpp
Go to the documentation of this file.
1/**
2 * @file VisualScriptEditorPanel_RenderingCore.cpp
3 * @brief Core rendering and undo/redo operations for VisualScriptEditorPanel.
4 * @author Olympe Engine
5 * @date 2026-03-09
6 *
7 * @details Extracted from VisualScriptEditorPanel.cpp (Phase 8 refactoring).
8 * Contains: PerformUndo, PerformRedo, Render, RenderContent, RenderToolbar, RenderSaveAsDialog.
9 * C++14 compliant — no std::optional, structured bindings, std::filesystem.
10 */
11
13#include "DebugController.h"
15#include "ConditionRegistry.h"
16#include "OperatorRegistry.h"
17#include "BBVariableRegistry.h"
18#include "MathOpOperand.h"
19#include "../system/system_utils.h"
20#include "../system/system_consts.h"
21#include "../NodeGraphCore/GlobalTemplateBlackboard.h"
22#include "../DataManager.h"
23
24#include "../third_party/imgui/imgui.h"
25#include "../third_party/imnodes/imnodes.h"
26#include "../json_helper.h"
27#include "../TaskSystem/TaskGraphLoader.h"
28
29#include <fstream>
30#include <iostream>
31#include <algorithm>
32#include <cmath>
33#include <cstring>
34#include <sstream>
35#include <iomanip>
36#include <cstdlib>
37#include <unordered_set>
38
39namespace Olympe {
40
41// ============================================================================
42// Undo/Redo wrappers
43// ============================================================================
44
46{
47 if (!m_undoStack.CanUndo())
48 return;
49
50 std::string desc = m_undoStack.PeekUndoDescription();
51 SYSTEM_LOG << "[VSEditor] UNDO: " << desc << "\n";
55
56 // Force-push the restored positions into ImNodes so that the next
57 // BeginNode()/EndNode() cycle renders them at the correct location.
58 // BUG-003 Fix: positions stored in m_editorNodes are grid-space
59 // (written by SyncNodePositionsFromImNodes via GetNodeGridSpacePos),
60 // so use SetNodeGridSpacePos to restore them pan-independently.
61 for (size_t i = 0; i < m_editorNodes.size(); ++i)
62 {
63 ImNodes::SetNodeGridSpacePos(
64 m_editorNodes[i].nodeID,
65 ImVec2(m_editorNodes[i].posX, m_editorNodes[i].posY));
66 }
67
68 // Block position sync and movement tracking for 1 frame so that stale
69 // ImNodes state cannot overwrite the correct undo-target positions before
70 // ImNodes has rendered the new layout at least once.
74 m_dirty = true;
75 m_verificationDone = false;
76 SYSTEM_LOG << "[VSEditor] Undo complete. Template now has "
77 << m_template.Nodes.size() << " nodes, "
78 << m_template.ExecConnections.size() << " exec connections\n";
79}
80
82{
83 if (!m_undoStack.CanRedo())
84 return;
85
86 std::string desc = m_undoStack.PeekRedoDescription();
87 SYSTEM_LOG << "[VSEditor] REDO: " << desc << "\n";
91
92 // Same treatment as PerformUndo().
93 // BUG-003 Fix: use SetNodeGridSpacePos (grid-space) for pan-independent restore.
94 for (size_t i = 0; i < m_editorNodes.size(); ++i)
95 {
96 ImNodes::SetNodeGridSpacePos(
97 m_editorNodes[i].nodeID,
98 ImVec2(m_editorNodes[i].posX, m_editorNodes[i].posY));
99 }
100
104 m_dirty = true;
105 m_verificationDone = false;
106 SYSTEM_LOG << "[VSEditor] Redo complete. Template now has "
107 << m_template.Nodes.size() << " nodes, "
108 << m_template.ExecConnections.size() << " exec connections\n";
109}
110
111// ============================================================================
112// Rendering
113// ============================================================================
114
116{
117 if (!m_visible)
118 return;
119
120 ImGui::Begin("VS Graph Editor", &m_visible);
122 ImGui::End();
123
124 // Render the condition preset library panel (Phase 24 UI integration)
125 m_libraryPanel->Render();
126}
127
129{
130 // Phase 26 — Performance Optimization: Verification is now MANUAL, not automatic
131 // Previously: Auto-verification was called every frame (CRITICAL BOTTLENECK)
132 // Now: Verification runs only when user clicks "Verify" button or saves
133 // This removes the O(n²) verification workload from the hot render path
134 // Impact: ~60 FPS frame drops → smooth UI, verification runs on-demand
135
138 ImGui::Separator();
139
140 // Two-column layout: canvas (left) | resize handle | properties panel (right, 3 sub-panels)
141 float totalWidth = ImGui::GetContentRegionAvail().x;
142
143 // Initialize panel width to default 28% on first use
144 if (m_propertiesPanelWidth <= 0.0f)
146
147 // Clamp to a sensible range
148 if (m_propertiesPanelWidth < 200.0f) m_propertiesPanelWidth = 200.0f;
150
151 float handleWidth = 6.0f;
153
154 ImGui::BeginChild("VSCanvas", ImVec2(canvasWidth, 0), false,
156 RenderCanvas();
157 ImGui::EndChild();
158
159 ImGui::SameLine();
160
161 // UX Fix #3: Drag-to-resize handle between canvas and properties panel
162 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.35f, 0.35f, 0.35f, 0.5f));
163 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.55f, 0.55f, 0.55f, 0.8f));
164 ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.70f, 0.70f, 0.70f, 1.0f));
165 ImGui::Button("##vsresize", ImVec2(handleWidth, -1.0f));
166 if (ImGui::IsItemHovered())
167 ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
168 if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left))
169 {
170 m_propertiesPanelWidth -= ImGui::GetIO().MouseDelta.x;
171 if (m_propertiesPanelWidth < 200.0f) m_propertiesPanelWidth = 200.0f;
173 }
174 ImGui::PopStyleColor(3);
175
176 ImGui::SameLine();
177
178 // Right panel container with tab-based layout
179 // Part A: Node Properties (top, resizable)
180 // Part B: Tab system for Presets, Local Variables, Global Variables
181 ImGui::BeginChild("VSRightPanel", ImVec2(m_propertiesPanelWidth, 0), true);
182
183 float rightPanelHeight = ImGui::GetContentRegionAvail().y;
184 float splitterHeight = 4.0f;
185
186 // Initialize sub-panel heights on first use
187 if (m_nodePropertiesPanelHeight <= 0.0f)
188 {
189 // Part A gets 35% of height, Part B (tabbed) gets remaining 65%
191 }
192
193 // Clamp heights to reasonable ranges
194 float minPanelHeight = 50.0f;
196
199
200 // ---- Part A: Node Properties Panel ----
201 ImGui::BeginChild("Part_A_NodeProps", ImVec2(0, m_nodePropertiesPanelHeight), false,
204 ImGui::EndChild();
205
206 // ---- Splitter (between Part A and Part B) ----
207 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.35f, 0.35f, 0.35f, 0.5f));
208 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.55f, 0.55f, 0.55f, 0.8f));
209 ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.70f, 0.70f, 0.70f, 1.0f));
210 ImGui::Button("##splitter_nodeprops_tabs", ImVec2(-1.0f, splitterHeight));
211 if (ImGui::IsItemHovered())
212 ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS);
213 if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left))
214 {
215 m_nodePropertiesPanelHeight += ImGui::GetIO().MouseDelta.y;
217 }
218 ImGui::PopStyleColor(3);
219
220 // ---- Part B: Tab-based panel (Presets, Local Variables, Global Variables) ----
221 ImGui::BeginChild("Part_B_TabbedPanel", ImVec2(0, tabbedPanelHeight), false,
223
224 // Phase 26: Render tab bar
226
227 // Phase 26: Render active tab content
229
230 ImGui::EndChild();
231
232 ImGui::EndChild(); // End VSRightPanel
233}
234
236{
237 // buttons: Save, Save As, Verify, Run Simulation
238 if (ImGui::Button("Save"))
239 {
240 SYSTEM_LOG << "[VisualScriptEditorPanel] Save button clicked. m_currentPath='"
241 << m_currentPath << "'\n";
242 if (m_currentPath.empty())
243 {
244 m_showSaveAsDialog = true;
245 }
246 else if (!Save())
247 {
248 ImGui::OpenPopup("SaveError");
249 }
250 }
251 ImGui::SameLine();
252 if (ImGui::Button("Save As"))
253 {
254 m_showSaveAsDialog = true;
255 }
256 ImGui::SameLine();
257 // Phase 24.3 — Removed "New Graph" button as requested
258 // Users must create new graphs through the file browser instead
259
260 if (ImGui::Button("Verify##gvs"))
261 {
263 }
264 ImGui::SameLine();
265 if (ImGui::Button("Run Graph##sim"))
266 {
268 }
269 //ImGui::SameLine(); // useless since the Panel proeprties button is now in the right panel tab bar
270 //if (ImGui::Button("Condition Presets"))
271 //{
272 // m_libraryPanel->Open();
273 //}
274 ImGui::SameLine();
275
276 // Phase 37 — Minimap controls for VisualScript canvas
277 if (m_canvasEditor)
278 {
279 // Checkbox to toggle minimap visibility
280 if (ImGui::Checkbox("Minimap##vs", &m_minimapVisible))
281 {
282 m_canvasEditor->SetMinimapVisible(m_minimapVisible);
283 }
284 ImGui::SameLine();
285
286 // DragFloat to control minimap size (0.05 to 0.5 of canvas)
287 if (ImGui::DragFloat("Size##minimap_vs", &m_minimapSize, 0.01f, 0.05f, 0.5f, "%.2f"))
288 {
289 m_minimapSize = std::max(0.05f, std::min(0.5f, m_minimapSize));
290 m_canvasEditor->SetMinimapSize(m_minimapSize);
291 }
292 ImGui::SameLine();
293
294 // Combo for minimap position
295 const char* positionLabels[] = { "Top-Left", "Top-Right", "Bottom-Left", "Bottom-Right" };
296 if (ImGui::Combo("Position##minimap_vs", &m_minimapPosition, positionLabels, 4))
297 {
298 m_canvasEditor->SetMinimapPosition(m_minimapPosition);
299 }
300 ImGui::SameLine();
301 }
302
303 // Title
304 const char* title = m_currentPath.empty()
305 ? "Untitled VS Graph"
306 : m_currentPath.c_str();
307 ImGui::TextDisabled("%s%s", title, m_dirty ? " *" : "");
308
309 ImGui::SameLine();
310
311
313 {
315 {
316 int errorCount = 0;
317 for (size_t i = 0; i < m_verificationResult.issues.size(); ++i)
318 {
320 ++errorCount;
321 }
322 ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
323 "[%d error(s)]", errorCount);
324 }
326 {
327 ImGui::TextColored(ImVec4(1.0f, 0.85f, 0.0f, 1.0f), "[OK - warnings]");
328 }
329 else
330 {
331 ImGui::TextColored(ImVec4(0.3f, 1.0f, 0.3f, 1.0f), "[OK]");
332 }
333 }
334
335 // Keyboard shortcuts
336 if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows))
337 {
338 if (ImGui::IsKeyPressed(ImGuiKey_S) &&
339 ImGui::GetIO().KeyCtrl)
340 {
341 SYSTEM_LOG << "[VisualScriptEditorPanel] Ctrl+S pressed. m_currentPath='"
342 << m_currentPath << "'\n";
343 if (m_currentPath.empty())
344 {
345 m_showSaveAsDialog = true;
346 }
347 else if (!Save())
348 {
349 ImGui::OpenPopup("SaveError");
350 }
351 }
352
353 // Undo (Ctrl+Z)
354 if (ImGui::GetIO().KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_Z) &&
356 {
357 PerformUndo();
358 }
359
360 // Redo (Ctrl+Y)
361 if (ImGui::GetIO().KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_Y) &&
363 {
364 PerformRedo();
365 }
366 }
367
368 if (ImGui::BeginPopup("SaveError"))
369 {
370 ImGui::TextColored(ImVec4(1,0,0,1), "Save failed — check file path.");
371 if (ImGui::Button("OK")) ImGui::CloseCurrentPopup();
372 ImGui::EndPopup();
373 }
374}
375
377{
379 {
380 // Use centralized SaveFilePickerModal via DataManager (Phase 40)
382 std::string suggestedName = m_currentPath.empty()
383 ? "graph"
384 : m_currentPath.substr(m_currentPath.find_last_of("/\\") + 1);
385
386 // Remove extension from suggested name
387 size_t dotPos = suggestedName.rfind('.');
388 if (dotPos != std::string::npos)
390
391 dm.OpenSaveFilePickerModal(Olympe::SaveFileType::Blueprint, "Gamedata/TaskGraph", suggestedName);
392 m_showSaveAsDialog = false;
393 }
394
395 // Render centralized save modal
397 dm.RenderSaveFilePickerModal();
398
399 // Handle modal result
400 if (dm.IsSaveFilePickerModalOpen() == false) {
401 std::string selectedFile = dm.GetSelectedSaveFile();
402 if (!selectedFile.empty()) {
403 SYSTEM_LOG << "[VisualScriptEditorPanel] SaveAs dialog confirmed. fullPath='"
404 << selectedFile << "'\n";
405 if (SaveAs(selectedFile)) {
406 std::cout << "[VisualScriptEditorPanel] Saved to: " << selectedFile << std::endl;
408 } else {
409 SYSTEM_LOG << "[VisualScriptEditorPanel] Save failed - check directory and permissions\n";
410 }
411 }
412 }
413}
414
415} // namespace Olympe
UI-side registry of available atomic tasks with display metadata.
Wrapper around the graph blackboard entries for dropdown editors.
Registry of available condition types for Branch/While node dropdowns.
Runtime debug controller for ATS Visual Scripting (Phase 5).
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
Defines MathOpOperand — operand references for MathOp nodes.
Hardcoded lists of math and comparison operators for dropdown editors.
ImNodes-based graph editor for ATS Visual Script graphs (Phase 5).
static DataManager & Get()
Definition DataManager.h:91
std::vector< TaskNodeDefinition > Nodes
All graph nodes.
std::vector< ExecPinConnection > ExecConnections
Explicit exec connections (ATS VS only)
bool CanUndo() const
Returns true if there is at least one command to undo.
bool CanRedo() const
Returns true if there is at least one command to redo.
std::string PeekUndoDescription() const
Returns the description of the top undo command, or "" if empty.
void Redo(TaskGraphTemplate &graph)
Re-applies the last undone command, moving it back to the undo stack.
std::string PeekRedoDescription() const
Returns the description of the top redo command, or "" if empty.
void Undo(TaskGraphTemplate &graph)
Undoes the last command, moving it to the redo stack.
UndoRedoStack m_undoStack
Undo/Redo command stack for reversible graph editing operations.
bool Save()
Saves the current canvas state to JSON v4 at the loaded path.
void RunGraphSimulation()
Simulates runtime execution of the current graph and logs traces.
void PerformRedo()
Re-applies the last undone command and syncs the canvas.
TaskGraphTemplate m_template
The template currently being edited.
float m_nodePropertiesPanelHeight
Height of the Node Properties panel (Part A) in the right panel.
void RenderRightPanelTabs()
Phase 26 — Tab system: Renders the tab bar for the 3-panel right section Displays tabs for Presets,...
bool m_skipPositionSyncNextFrame
Set to true by Undo/Redo; causes next frame to skip SyncNodePositionsFromImNodes() so that the positi...
void PerformUndo()
Undoes the last command and syncs the canvas (nodes + links).
void Render()
Renders the full panel window.
void RenderContent()
Renders panel content without window wrapper - for fixed layout.
void RenderNodePropertiesPanel()
Part A: Node Properties panel (top-left of right panel)
VSVerificationResult m_verificationResult
Latest verification result (produced by RunVerification())
void RunVerification()
Runs VSGraphVerifier on the current graph and stores the result.
bool SaveAs(const std::string &path)
Saves the current canvas state to a new JSON v4 file.
bool m_showSaveAsDialog
True when the "Save As" modal should be opened next frame.
std::unique_ptr< ImNodesCanvasEditor > m_canvasEditor
Canvas editor adapter for minimap support (Phase 37) Abstracts imnodes minimap rendering through ICan...
void RebuildLinks()
Rebuilds ImNodes exec/data link arrays from the template.
void RenderRightPanelTabContent()
Phase 26 — Tab system: Renders the content of the active tab Dispatches to appropriate render functio...
int m_minimapPosition
Minimap position (0=TopLeft, 1=TopRight, 2=BottomLeft, 3=BottomRight)
float m_propertiesPanelWidth
Width of the properties+blackboard panel on the right.
bool m_justPerformedUndoRedo
Set to true immediately after Undo/Redo; blocks node movement tracking for 1 frame to allow ImNodes t...
std::unordered_map< int, std::pair< float, float > > m_nodeDragStartPositions
Per-node drag-start positions used to record a single MoveNodeCommand per drag gesture instead of one...
bool m_verificationDone
True once RunVerification() has been called at least once for the current graph.
float m_minimapSize
Minimap size ratio (0.05-0.5 of canvas)
std::vector< VSEditorNode > m_editorNodes
Editor nodes (mirrors m_template.Nodes + position/selection state)
bool m_minimapVisible
Minimap visibility flag for VisualScript canvas.
void SyncEditorNodesFromTemplate()
Rebuilds m_editorNodes from m_template, preserving existing node positions.
std::unique_ptr< ConditionPresetLibraryPanel > m_libraryPanel
Global condition preset library panel (UI for creating/editing/deleting presets).
< Provides AssetID and INVALID_ASSET_ID
@ Blueprint
.ats files (SubGraph/VisualScript)
std::vector< VSVerificationIssue > issues
#define SYSTEM_LOG