Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
AIEditorGUI.cpp
Go to the documentation of this file.
1/**
2 * @file AIEditorGUI.cpp
3 * @brief Implementation of AIEditorGUI
4 * @author Olympe Engine
5 * @date 2026-02-18
6 */
7
8#include "AIEditorGUI.h"
9#include "AIEditorClipboard.h"
10#include "AIEditorFileDialog.h"
12#include "../../system/system_utils.h"
13#include "../../third_party/imgui/imgui.h"
14#include "../../third_party/imnodes/imnodes.h"
15#include "../AIGraphPlugin_BT/BTGraphValidator.h"
16#include "../../NodeGraphCore/Commands/CreateNodeCommand.h"
17#include "../../NodeGraphCore/Commands/ConnectPinsCommand.h"
18#include "../../NodeGraphCore/Commands/ToggleNodeBreakpointCommand.h"
19#include <cstring>
20
24
25namespace Olympe {
26namespace AI {
27
28// ============================================================================
29// Constructor / Destructor
30// ============================================================================
31
33 : m_isActive(false)
34 , m_showNodePalette(true)
35 , m_showBlackboardPanel(false)
36 , m_showSensesPanel(false)
37 , m_showRuntimeDebugPanel(false)
38 , m_imnodesContext(nullptr)
39{
40 std::memset(m_assetSearchFilter, 0, sizeof(m_assetSearchFilter));
41}
42
47
48// ============================================================================
49// Initialize / Shutdown
50// ============================================================================
51
53{
54 SYSTEM_LOG << "[AIEditorGUI] Initializing..." << std::endl;
55
56 // Create ImNodes context
57 m_imnodesContext = ImNodes::CreateContext();
58 if (m_imnodesContext == nullptr) {
59 SYSTEM_LOG << "[AIEditorGUI] ERROR: Failed to create ImNodes context" << std::endl;
60 return false;
61 }
62 ImNodes::SetCurrentContext(static_cast<ImNodesContext*>(m_imnodesContext));
63
64 // Initialize BTNodeRegistry
66 auto allTypes = registry.GetAllNodeTypes();
67 SYSTEM_LOG << "[AIEditorGUI] Loaded " << allTypes.size() << " node types" << std::endl;
68
69 // Create BTNodePalette
70 m_nodePalette.reset(new BTNodePalette());
71
72 // Scan AI graph directory
73 ScanAIGraphDirectory("Blueprints/AI/");
74
75 m_isActive = true;
76
77 SYSTEM_LOG << "[AIEditorGUI] Initialization complete" << std::endl;
78 return true;
79}
80
82{
83 SYSTEM_LOG << "[AIEditorGUI] Shutting down..." << std::endl;
84
85 // Destroy ImNodes context
86 if (m_imnodesContext != nullptr) {
87 ImNodes::DestroyContext(static_cast<ImNodesContext*>(m_imnodesContext));
88 m_imnodesContext = nullptr;
89 }
90
91 // Clear command stack
93
94 // Clear node palette
95 m_nodePalette.reset();
96
97 m_isActive = false;
98}
99
100// ============================================================================
101// Update / Render
102// ============================================================================
103
104void AIEditorGUI::Update(float deltaTime)
105{
106 (void)deltaTime;
107
108 // Early exit if editor is not active
109 // When m_isActive is false, the editor is minimized or closed and should not process input
110 if (!m_isActive) {
111 return;
112 }
113
114 // Handle keyboard shortcuts
115 ImGuiIO& io = ImGui::GetIO();
116 bool ctrlPressed = io.KeyCtrl;
117 bool shiftPressed = io.KeyShift;
118
119 // Ctrl+Z - Undo
120 if (ctrlPressed && !shiftPressed && ImGui::IsKeyPressed(ImGuiKey_Z)) {
122 }
123
124 // Ctrl+Y or Ctrl+Shift+Z - Redo
125 if ((ctrlPressed && ImGui::IsKeyPressed(ImGuiKey_Y)) ||
126 (ctrlPressed && shiftPressed && ImGui::IsKeyPressed(ImGuiKey_Z))) {
128 }
129
130 // Ctrl+C - Copy
131 if (ctrlPressed && !shiftPressed && ImGui::IsKeyPressed(ImGuiKey_C)) {
133 }
134
135 // Ctrl+X - Cut
136 if (ctrlPressed && !shiftPressed && ImGui::IsKeyPressed(ImGuiKey_X)) {
138 }
139
140 // Ctrl+V - Paste
141 if (ctrlPressed && !shiftPressed && ImGui::IsKeyPressed(ImGuiKey_V)) {
143 }
144
145 // Delete - Delete selected
146 if (ImGui::IsKeyPressed(ImGuiKey_Delete)) {
148 }
149
150 // Ctrl+A - Select All
151 if (ctrlPressed && !shiftPressed && ImGui::IsKeyPressed(ImGuiKey_A)) {
153 }
154
155 // Ctrl+N - New BT
156 if (ctrlPressed && !shiftPressed && ImGui::IsKeyPressed(ImGuiKey_N)) {
158 }
159
160 // Ctrl+S - Save
161 if (ctrlPressed && !shiftPressed && ImGui::IsKeyPressed(ImGuiKey_S)) {
163 }
164
165 // Ctrl+Shift+S - Save As
166 if (ctrlPressed && shiftPressed && ImGui::IsKeyPressed(ImGuiKey_S)) {
168 }
169
170 // Ctrl+O - Open
171 if (ctrlPressed && !shiftPressed && ImGui::IsKeyPressed(ImGuiKey_O)) {
173 }
174
175 // Ctrl+W - Close
176 if (ctrlPressed && !shiftPressed && ImGui::IsKeyPressed(ImGuiKey_W)) {
178 }
179
180 // F9 - Toggle breakpoint on selected node (Phase 2.0)
181 if (ImGui::IsKeyPressed(ImGuiKey_F9) && !m_selectedNodeIds.empty())
182 {
184 NodeGraph::GraphDocument* doc = mgr.GetActiveGraph();
185 if (doc != nullptr)
186 {
187 int nodeId = m_selectedNodeIds[0];
188 auto cmd = std::unique_ptr<NodeGraph::ICommand>(
190 &doc->GetNodeAnnotations(), nodeId));
192 }
193 }
194}
195
197{
198 if (!m_isActive) {
199 return;
200 }
201
202 // Main window
203 ImGui::SetNextWindowPos(ImVec2(0, 0));
204 ImGui::SetNextWindowSize(ImGui::GetIO().DisplaySize);
205
212
213 ImGui::Begin("AI Editor", nullptr, flags);
214
215 // Menu bar
217
218 // 3-panel layout
219 ImGui::BeginChild("AssetBrowser", ImVec2(250, 0), true);
221 ImGui::EndChild();
222
223 ImGui::SameLine();
224
225 ImGui::BeginChild("NodeGraph", ImVec2(-350, 0), true);
227 ImGui::EndChild();
228
229 ImGui::SameLine();
230
231 ImGui::BeginChild("Inspector", ImVec2(0, 0), true);
233 ImGui::EndChild();
234
235 ImGui::End();
236
237 // Render node palette
240 }
241
242 // Render specialized panels
245 }
246
247 if (m_showSensesPanel) {
249 }
250
253 }
254}
255
256// ============================================================================
257// Menu Bar
258// ============================================================================
259
261{
262 if (ImGui::BeginMenuBar()) {
263 // File Menu
264 if (ImGui::BeginMenu("File")) {
265 if (ImGui::MenuItem("New Behavior Tree", "Ctrl+N")) {
267 }
268 if (ImGui::MenuItem("New HFSM", "Ctrl+Shift+N")) {
270 }
271 ImGui::Separator();
272 if (ImGui::MenuItem("Open", "Ctrl+O")) {
274 }
275 if (ImGui::MenuItem("Save", "Ctrl+S")) {
277 }
278 if (ImGui::MenuItem("Save As", "Ctrl+Shift+S")) {
280 }
281 ImGui::Separator();
282 if (ImGui::MenuItem("Close", "Ctrl+W")) {
284 }
285 ImGui::EndMenu();
286 }
287
288 // Edit Menu
289 if (ImGui::BeginMenu("Edit")) {
290 bool canUndo = m_commandStack.CanUndo();
291 bool canRedo = m_commandStack.CanRedo();
292
293 if (ImGui::MenuItem("Undo", "Ctrl+Z", false, canUndo)) {
295 }
296 if (ImGui::MenuItem("Redo", "Ctrl+Y", false, canRedo)) {
298 }
299 ImGui::Separator();
300 if (ImGui::MenuItem("Cut", "Ctrl+X")) {
302 }
303 if (ImGui::MenuItem("Copy", "Ctrl+C")) {
305 }
306 if (ImGui::MenuItem("Paste", "Ctrl+V")) {
308 }
309 if (ImGui::MenuItem("Delete", "Delete")) {
311 }
312 ImGui::Separator();
313 if (ImGui::MenuItem("Select All", "Ctrl+A")) {
315 }
316 ImGui::EndMenu();
317 }
318
319 // View Menu
320 if (ImGui::BeginMenu("View")) {
321 ImGui::MenuItem("Node Palette", nullptr, &m_showNodePalette);
322 ImGui::MenuItem("Blackboard", nullptr, &m_showBlackboardPanel);
323 ImGui::MenuItem("Senses Debug", nullptr, &m_showSensesPanel);
324 ImGui::MenuItem("Runtime Debug", nullptr, &m_showRuntimeDebugPanel);
325 ImGui::Separator();
326 if (ImGui::MenuItem("Reset Layout")) {
328 }
329 if (ImGui::MenuItem("Auto Layout")) {
331 }
332 ImGui::EndMenu();
333 }
334
335 // Help Menu
336 if (ImGui::BeginMenu("Help")) {
337 if (ImGui::MenuItem("About")) {
339 }
340 ImGui::EndMenu();
341 }
342
343 ImGui::EndMenuBar();
344 }
345}
346
347// ============================================================================
348// Asset Browser
349// ============================================================================
350
352{
353 ImGui::Text("Asset Browser");
354 ImGui::Separator();
355
356 // Search filter
357 ImGui::InputText("Search", m_assetSearchFilter, sizeof(m_assetSearchFilter));
358
359 ImGui::Separator();
360
361 // Asset list
362 for (size_t i = 0; i < m_assetFiles.size(); ++i) {
363 const std::string& file = m_assetFiles[i];
364
365 // Apply search filter
366 if (m_assetSearchFilter[0] != '\0') {
367 if (file.find(m_assetSearchFilter) == std::string::npos) {
368 continue;
369 }
370 }
371
373 }
374}
375
377{
378 // For now, just clear the list
379 // In a real implementation, this would scan the filesystem
380 m_assetFiles.clear();
381 SYSTEM_LOG << "[AIEditorGUI] Scanned " << directory << std::endl;
382}
383
384void AIEditorGUI::RenderAssetEntry(const std::string& filename, const std::string& fullPath)
385{
386 if (ImGui::Selectable(filename.c_str())) {
387 SYSTEM_LOG << "[AIEditorGUI] Selected asset: " << fullPath << std::endl;
388 // TODO: Load asset on double-click
389 }
390}
391
392// ============================================================================
393// Node Graph
394// ============================================================================
395
397{
398 ImGui::Text("Node Graph");
399 ImGui::Separator();
400
401 // Get active graph
403 NodeGraph::GraphDocument* doc = mgr.GetActiveGraph();
404
405 if (doc == nullptr) {
406 ImGui::Text("No active graph. Create a new one from File menu.");
407 return;
408 }
409
410 // Render canvas
412}
413
415{
416 ImNodes::SetCurrentContext(static_cast<ImNodesContext*>(m_imnodesContext));
417 ImNodes::BeginNodeEditor();
418
419 // Get active graph
421 NodeGraph::GraphDocument* doc = mgr.GetActiveGraph();
422
423 if (doc != nullptr) {
424 // Render nodes
425 const std::vector<NodeGraph::NodeData>& nodes = doc->GetNodes();
426 for (size_t i = 0; i < nodes.size(); ++i) {
427 RenderNode(nodes[i].id);
428 }
429
430 // Render connections
432 }
433
434 ImNodes::EndNodeEditor();
435
436 // Handle interactions
440}
441
443{
444 // Get node data
446 NodeGraph::GraphDocument* doc = mgr.GetActiveGraph();
447 if (doc == nullptr) {
448 return;
449 }
450
451 const NodeGraph::NodeData* nodeData = doc->GetNode(nodeId);
452 if (nodeData == nullptr) {
453 return;
454 }
455
456 // Get annotation for this node (Phase 2.0)
458 doc->GetNodeAnnotations().GetAnnotation(static_cast<int>(nodeId.value));
459
460 // Render using dedicated renderer
461 bool isSelected = false;
462 for (size_t i = 0; i < m_selectedNodeIds.size(); ++i) {
463 if (m_selectedNodeIds[i] == static_cast<int>(nodeId.value)) {
464 isSelected = true;
465 break;
466 }
467 }
468
469 AIEditorNodeRenderer::RenderNode(*nodeData, isSelected, false, annotation);
470}
471
473{
474 // Get active graph
476 NodeGraph::GraphDocument* doc = mgr.GetActiveGraph();
477 if (doc == nullptr) {
478 return;
479 }
480
481 const std::vector<NodeGraph::LinkData>& links = doc->GetLinks();
482 for (size_t i = 0; i < links.size(); ++i) {
483 int linkId = static_cast<int>(links[i].id.value);
484 int fromPin = static_cast<int>(links[i].fromPin.value);
485 int toPin = static_cast<int>(links[i].toPin.value);
486
487 ImNodes::Link(linkId, fromPin, toPin);
488 }
489}
490
492{
493 // Check if node palette is dragging
494 if (m_nodePalette && m_nodePalette->IsDragging()) {
495 // TODO: Handle node creation from palette drag
496 }
497}
498
500{
501 // Get selected nodes
502 int numSelected = ImNodes::NumSelectedNodes();
503 if (numSelected > 0) {
504 m_selectedNodeIds.resize(static_cast<size_t>(numSelected));
505 ImNodes::GetSelectedNodes(m_selectedNodeIds.data());
506 }
507
508 // Get selected links
509 int numSelectedLinks = ImNodes::NumSelectedLinks();
510 if (numSelectedLinks > 0) {
511 m_selectedLinkIds.resize(static_cast<size_t>(numSelectedLinks));
512 ImNodes::GetSelectedLinks(m_selectedLinkIds.data());
513 }
514}
515
517{
518 int startPin = -1;
519 int endPin = -1;
520
521 if (ImNodes::IsLinkCreated(&startPin, &endPin)) {
522 SYSTEM_LOG << "[AIEditorGUI] Link created: " << startPin << " -> " << endPin << std::endl;
523
524 // TODO: Create ConnectPinsCommand and execute
525 // For now, just connect directly
527 NodeGraph::GraphDocument* doc = mgr.GetActiveGraph();
528 if (doc != nullptr) {
529 NodeGraph::PinId fromPin;
530 fromPin.value = static_cast<uint32_t>(startPin);
531 NodeGraph::PinId toPin;
532 toPin.value = static_cast<uint32_t>(endPin);
533
534 doc->ConnectPins(fromPin, toPin);
535 }
536 }
537}
538
539// ============================================================================
540// Inspector
541// ============================================================================
542
544{
545 ImGui::Text("Inspector");
546 ImGui::Separator();
547
548 // Show selected node properties
549 if (!m_selectedNodeIds.empty()) {
550 ImGui::Text("Selected Node: %d", m_selectedNodeIds[0]);
551 // TODO: Show node properties
552 } else {
553 ImGui::Text("No selection");
554 }
555}
556
557// ============================================================================
558// AI-Specific Panels
559// ============================================================================
560
569
571{
572 ImGui::Begin("AI Senses Debug", &m_showSensesPanel);
573
574 ImGui::Text("AI Senses");
575 ImGui::Separator();
576
577 // TODO: Implement senses debug
578 ImGui::Text("(Not yet implemented)");
579
580 ImGui::End();
581}
582
584{
585 ImGui::Begin("Runtime Debug", &m_showRuntimeDebugPanel);
586
587 ImGui::Text("Runtime Execution");
588 ImGui::Separator();
589
590 // TODO: Implement runtime debug
591 ImGui::Text("(Not yet implemented)");
592
593 ImGui::End();
594}
595
596// ============================================================================
597// Menu Actions
598// ============================================================================
599
601{
602 SYSTEM_LOG << "[AIEditorGUI] Creating new Behavior Tree" << std::endl;
603
605 NodeGraph::GraphId id = mgr.CreateGraph("AIGraph", "BehaviorTree");
606 mgr.SetActiveGraph(id);
607}
608
610{
611 SYSTEM_LOG << "[AIEditorGUI] Creating new HFSM" << std::endl;
612
614 NodeGraph::GraphId id = mgr.CreateGraph("AIGraph", "HFSM");
615 mgr.SetActiveGraph(id);
616}
617
619{
620 SYSTEM_LOG << "[AIEditorGUI] Open file dialog" << std::endl;
621
622 std::string filepath = AIEditorFileDialog::OpenFile(m_lastOpenPath);
623 if (!filepath.empty()) {
624 m_lastOpenPath = filepath.substr(0, filepath.find_last_of("\\/"));
625
626 // Load graph via NodeGraphManager
628 NodeGraph::GraphId id = mgr.LoadGraph(filepath);
629 if (id.value != 0) {
630 mgr.SetActiveGraph(id);
631 SYSTEM_LOG << "[AIEditorGUI] Loaded: " << filepath << std::endl;
632 } else {
633 SYSTEM_LOG << "[AIEditorGUI] ERROR: Failed to load: " << filepath << std::endl;
634 }
635 }
636}
637
639{
641 NodeGraph::GraphDocument* doc = mgr.GetActiveGraph();
642
643 if (doc == nullptr) {
644 SYSTEM_LOG << "[AIEditorGUI] No active graph to save" << std::endl;
645 return;
646 }
647
648 NodeGraph::GraphId activeId = mgr.GetActiveGraphId();
649
650 // Try to save with existing filepath
651 // In a real implementation, this would use the graph's stored filepath
652 // For now, save to a default location
653 std::string filepath = "Blueprints/AI/autosave_bt.json";
654
655 bool success = mgr.SaveGraph(activeId, filepath);
656
657 if (success) {
658 SYSTEM_LOG << "[AIEditorGUI] Saved to: " << filepath << std::endl;
659 } else {
660 SYSTEM_LOG << "[AIEditorGUI] ERROR: Save failed" << std::endl;
661 }
662}
663
665{
666 SYSTEM_LOG << "[AIEditorGUI] Save As file dialog" << std::endl;
667
669 NodeGraph::GraphDocument* doc = mgr.GetActiveGraph();
670
671 if (doc == nullptr) {
672 SYSTEM_LOG << "[AIEditorGUI] No active graph to save" << std::endl;
673 return;
674 }
675
676 std::string filepath = AIEditorFileDialog::SaveFile(m_lastSavePath, "*.bt.json");
677 if (!filepath.empty()) {
678 m_lastSavePath = filepath.substr(0, filepath.find_last_of("\\/"));
679 NodeGraph::GraphId activeId = mgr.GetActiveGraphId();
680 mgr.SaveGraph(activeId, filepath);
681 SYSTEM_LOG << "[AIEditorGUI] Saved to: " << filepath << std::endl;
682 }
683}
684
686{
688 NodeGraph::GraphId activeId = mgr.GetActiveGraphId();
689
690 if (activeId.value == 0)
691 {
692 SYSTEM_LOG << "[AIEditorGUI] Auto-layout failed: No active graph" << std::endl;
693 return;
694 }
695
697 if (doc == nullptr)
698 {
699 SYSTEM_LOG << "[AIEditorGUI] Auto-layout failed: Document not found" << std::endl;
700 return;
701 }
702
703 // Use default layout configuration
705
706 // Apply auto-layout
707 bool success = doc->AutoLayout(config);
708
709 if (success)
710 {
711 SYSTEM_LOG << "[AIEditorGUI] Auto-layout applied successfully" << std::endl;
712 }
713 else
714 {
715 SYSTEM_LOG << "[AIEditorGUI] Auto-layout failed - check graph has root node" << std::endl;
716 }
717}
718
720{
721 SYSTEM_LOG << "[AIEditorGUI] Close current graph" << std::endl;
722
724 NodeGraph::GraphId activeId = mgr.GetActiveGraphId();
725 if (activeId.value != 0) {
726 mgr.CloseGraph(activeId);
727 }
728}
729
731{
732 if (m_commandStack.CanUndo()) {
734 SYSTEM_LOG << "[AIEditorGUI] Undo: " << m_commandStack.GetUndoDescription() << std::endl;
735 }
736}
737
739{
740 if (m_commandStack.CanRedo()) {
742 SYSTEM_LOG << "[AIEditorGUI] Redo: " << m_commandStack.GetRedoDescription() << std::endl;
743 }
744}
745
747{
749 NodeGraph::GraphDocument* doc = mgr.GetActiveGraph();
750 if (doc == nullptr) {
751 SYSTEM_LOG << "[AIEditorGUI] No active graph for cut" << std::endl;
752 return;
753 }
754
755 // Get selected nodes
756 std::vector<NodeGraph::NodeId> selectedNodes;
757 for (size_t i = 0; i < m_selectedNodeIds.size(); ++i) {
758 NodeGraph::NodeId nodeId;
759 nodeId.value = static_cast<uint32_t>(m_selectedNodeIds[i]);
760 selectedNodes.push_back(nodeId);
761 }
762
763 if (selectedNodes.empty()) {
764 SYSTEM_LOG << "[AIEditorGUI] No nodes selected for cut" << std::endl;
765 return;
766 }
767
768 // Cut via clipboard
769 AIEditorClipboard::Get().Cut(selectedNodes, doc);
770
771 // Clear selection
772 m_selectedNodeIds.clear();
773
774 SYSTEM_LOG << "[AIEditorGUI] Cut " << selectedNodes.size() << " nodes" << std::endl;
775}
776
778{
780 NodeGraph::GraphDocument* doc = mgr.GetActiveGraph();
781 if (doc == nullptr) {
782 SYSTEM_LOG << "[AIEditorGUI] No active graph for copy" << std::endl;
783 return;
784 }
785
786 // Get selected nodes
787 std::vector<NodeGraph::NodeId> selectedNodes;
788 for (size_t i = 0; i < m_selectedNodeIds.size(); ++i) {
789 NodeGraph::NodeId nodeId;
790 nodeId.value = static_cast<uint32_t>(m_selectedNodeIds[i]);
791 selectedNodes.push_back(nodeId);
792 }
793
794 if (selectedNodes.empty()) {
795 SYSTEM_LOG << "[AIEditorGUI] No nodes selected for copy" << std::endl;
796 return;
797 }
798
799 // Copy via clipboard
800 AIEditorClipboard::Get().Copy(selectedNodes, doc);
801
802 SYSTEM_LOG << "[AIEditorGUI] Copied " << selectedNodes.size() << " nodes" << std::endl;
803}
804
806{
808 NodeGraph::GraphDocument* doc = mgr.GetActiveGraph();
809 if (doc == nullptr) {
810 SYSTEM_LOG << "[AIEditorGUI] No active graph for paste" << std::endl;
811 return;
812 }
813
814 if (AIEditorClipboard::Get().IsEmpty()) {
815 SYSTEM_LOG << "[AIEditorGUI] Clipboard is empty" << std::endl;
816 return;
817 }
818
819 // Paste with 50px offset
820 Vector pasteOffset(50.0f, 50.0f);
821 std::vector<NodeGraph::NodeId> newNodeIds = AIEditorClipboard::Get().Paste(doc, pasteOffset);
822
823 // Select pasted nodes
824 m_selectedNodeIds.clear();
825 for (size_t i = 0; i < newNodeIds.size(); ++i) {
826 m_selectedNodeIds.push_back(static_cast<int>(newNodeIds[i].value));
827 }
828
829 SYSTEM_LOG << "[AIEditorGUI] Pasted " << newNodeIds.size() << " nodes" << std::endl;
830}
831
833{
835 NodeGraph::GraphDocument* doc = mgr.GetActiveGraph();
836 if (doc == nullptr) {
837 SYSTEM_LOG << "[AIEditorGUI] No active graph for delete" << std::endl;
838 return;
839 }
840
841 if (m_selectedNodeIds.empty()) {
842 SYSTEM_LOG << "[AIEditorGUI] No nodes selected for deletion" << std::endl;
843 return;
844 }
845
846 // Delete selected nodes
847 for (size_t i = 0; i < m_selectedNodeIds.size(); ++i) {
848 NodeGraph::NodeId nodeId;
849 nodeId.value = static_cast<uint32_t>(m_selectedNodeIds[i]);
850 doc->DeleteNode(nodeId);
851 }
852
853 SYSTEM_LOG << "[AIEditorGUI] Deleted " << m_selectedNodeIds.size() << " nodes" << std::endl;
854
855 // Clear selection
856 m_selectedNodeIds.clear();
857}
858
860{
862 NodeGraph::GraphDocument* doc = mgr.GetActiveGraph();
863 if (doc == nullptr) {
864 SYSTEM_LOG << "[AIEditorGUI] No active graph for select all" << std::endl;
865 return;
866 }
867
868 // Clear current selection
869 m_selectedNodeIds.clear();
870
871 // Select all nodes
872 const std::vector<NodeGraph::NodeData>& nodes = doc->GetNodes();
873 for (size_t i = 0; i < nodes.size(); ++i) {
874 m_selectedNodeIds.push_back(static_cast<int>(nodes[i].id.value));
875 }
876
877 SYSTEM_LOG << "[AIEditorGUI] Selected all " << m_selectedNodeIds.size() << " nodes" << std::endl;
878}
879
881{
882 SYSTEM_LOG << "[AIEditorGUI] Reset Layout" << std::endl;
883
884 // Reset panel visibility
885 m_showNodePalette = true;
886 m_showBlackboardPanel = false;
887 m_showSensesPanel = false;
889}
890
895
900
905
910
912{
913 SYSTEM_LOG << "[AIEditorGUI] About dialog (not yet implemented)" << std::endl;
914}
915
916// ============================================================================
917// Helper Methods
918// ============================================================================
919
920std::string AIEditorGUI::ExtractDirectory(const std::string& filepath)
921{
922 size_t lastSlash = filepath.find_last_of("/\\");
923 if (lastSlash != std::string::npos) {
924 return filepath.substr(0, lastSlash);
925 }
926 return std::string();
927}
928
929std::string AIEditorGUI::ExtractFilename(const std::string& filepath)
930{
931 size_t lastSlash = filepath.find_last_of("/\\");
932 if (lastSlash != std::string::npos) {
933 return filepath.substr(lastSlash + 1);
934 }
935 return filepath;
936}
937
938bool AIEditorGUI::EndsWith(const std::string& str, const std::string& suffix)
939{
940 if (suffix.size() > str.size()) {
941 return false;
942 }
943
944 // Case-insensitive comparison for file extensions
945 std::string strLower = str.substr(str.size() - suffix.size());
946 std::string suffixLower = suffix;
947
948 // Convert to lowercase
949 for (size_t i = 0; i < strLower.size(); ++i) {
950 if (strLower[i] >= 'A' && strLower[i] <= 'Z') {
951 strLower[i] = strLower[i] + ('a' - 'A');
952 }
953 }
954 for (size_t i = 0; i < suffixLower.size(); ++i) {
955 if (suffixLower[i] >= 'A' && suffixLower[i] <= 'Z') {
956 suffixLower[i] = suffixLower[i] + ('a' - 'A');
957 }
958 }
959
960 return strLower == suffixLower;
961}
962
963} // namespace AI
964} // namespace Olympe
Clipboard system for AI Editor (Cut/Copy/Paste)
Native file dialog wrapper for AI Editor (Phase 1.5)
Main GUI class for AI Editor (Phase 1.3)
Node renderer for AI Editor with ImNodes.
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
void Cut(const std::vector< NodeGraph::NodeId > &nodeIds, NodeGraph::GraphDocument *doc)
Cut selected nodes (copy + delete)
static AIEditorClipboard & Get()
Get singleton instance.
void Copy(const std::vector< NodeGraph::NodeId > &nodeIds, NodeGraph::GraphDocument *doc)
Copy selected nodes to clipboard.
std::vector< NodeGraph::NodeId > Paste(NodeGraph::GraphDocument *doc, Vector pasteOffset)
Paste clipboard nodes into active graph.
static std::string OpenFile(const std::string &filterList="json,btree", const std::string &defaultPath="")
Open a native file dialog to select an existing file.
static std::string SaveFile(const std::string &filterList="json,btree", const std::string &defaultPath="", const std::string &defaultName="new_ai_graph.json")
Open a native save file dialog.
static std::string ExtractDirectory(const std::string &filepath)
Extract directory from filepath.
void RenderInspector()
Render inspector panel (right)
void HandleNodeCreation()
Handle node creation from palette.
void RenderNodeGraphCanvas()
Render node graph with ImNodes.
std::unique_ptr< BTNodePalette > m_nodePalette
std::vector< int > m_selectedLinkIds
void Shutdown()
Shutdown and cleanup.
std::vector< std::string > m_assetFiles
static bool EndsWith(const std::string &str, const std::string &suffix)
Check if string ends with suffix.
static std::string ExtractFilename(const std::string &filepath)
Extract filename from filepath.
void RenderRuntimeDebugPanel()
Render runtime debug panel (entity list + execution)
void Render()
Render the complete UI.
void RenderNode(NodeGraph::NodeId nodeId)
Render a single node.
void HandleLinkCreation()
Handle link creation.
void RenderBlackboardPanel()
Render blackboard inspector panel.
BlackboardPanel m_blackboardPanel
void RenderMenuBar()
Render main menu bar.
void RenderSensesPanel()
Render AI senses debug panel.
void HandleNodeSelection()
Handle node selection.
void RenderNodeGraph()
Render node graph panel (center)
void RenderConnections()
Render connections between nodes.
std::vector< int > m_selectedNodeIds
void RenderAssetEntry(const std::string &filename, const std::string &fullPath)
Render asset entry.
void ScanAIGraphDirectory(const std::string &directory)
Scan directory for AI graphs.
void Update(float deltaTime)
Update (called per frame)
bool Initialize()
Initialize the editor.
void RenderAssetBrowser()
Render asset browser panel (left)
NodeGraph::CommandStack m_commandStack
static void RenderNode(const NodeGraph::NodeData &nodeData, bool isSelected=false, bool isExecuting=false, const NodeGraph::NodeAnnotation *annotation=nullptr)
Render a single node with ImNodes.
UI palette for BT node selection.
Singleton registry for all BT node types.
static BTNodeRegistry & Get()
Get singleton instance.
void Render(NodeGraph::BlackboardSystem *blackboard, bool *pOpen=nullptr)
Render the panel.
Manages named blackboard variables for a graph.
void Undo()
Undo the last command.
bool CanUndo() const
Check if undo is available.
void Clear()
Clear all commands.
std::string GetUndoDescription() const
Get description of next undo command.
std::string GetRedoDescription() const
Get description of next redo command.
void Redo()
Redo the last undone command.
bool CanRedo() const
Check if redo is available.
void ExecuteCommand(std::unique_ptr< ICommand > cmd)
Execute a command and add to undo stack.
Main document class for a node graph.
const std::vector< LinkData > & GetLinks() const
const std::vector< NodeData > & GetNodes() const
Singleton manager for multiple node graphs.
static NodeGraphManager & Get()
Get singleton instance.
Toggles the breakpoint state of a node (undo/redo-able)
const char *const DEFAULT_AI_GRAPH_NAME
const char *const DEFAULT_AI_GRAPH_FILTER
const char *const DEFAULT_AI_GRAPH_EXT
< Provides AssetID and INVALID_ASSET_ID
Holds annotation data for a single node.
#define SYSTEM_LOG