15#include "Panels/MainPanel.h"
16#include "Panels/RightSidePanel.h"
26#include "Tabs/PropertiesTab.h"
37#include <wx/artprov.h>
38#include <wx/aui/aui.h>
39#include <wx/filename.h>
40#include <wx/splitter.h>
41#include <wx/stdpaths.h>
74 MainFrame::
MainFrame(const wxString &title, const wxPoint &pos, const wxSize &size,
long style)
75 : wxFrame(NULL, wxID_ANY, title, pos, size, style), m_auiManager(
nullptr), m_bottomPanel(
nullptr),
76 m_scenePanel(
nullptr), m_panelToggleToolbar(
nullptr) {
78 m_auiManager =
new wxAuiManager(
this);
81 m_auiManager->GetArtProvider()->SetMetric(wxAUI_DOCKART_SASH_SIZE, 8);
83 LOG_INFO(
"AUI",
"Initialized unified AUI manager for MainFrame");
88 if (prefs.LoadFromFile(configPath)) {
89 LOG_INFO(
"Preferences",
"Application preferences loaded from: " + configPath);
91 LOG_INFO(
"Preferences",
"Using default preferences (config file not found or invalid)");
94 CreateMenuAndStatusBar();
95 CreatePanelToggleToolbar();
96 InitializeConfigurationMenu();
100 const auto &windowSettings = prefs.GetWindowSettings();
103 if (windowSettings.showStatusBar) {
104 GetStatusBar()->Show();
105 LOG_INFO(
"Preferences",
"Status bar enabled");
107 GetStatusBar()->Hide();
108 LOG_INFO(
"Preferences",
"Status bar hidden");
110 switch (windowSettings.startupMode) {
114 LOG_INFO(
"Preferences",
"Starting in maximized mode");
118 LOG_INFO(
"Preferences",
"Starting in normal windowed mode");
123 if (windowSettings.alwaysOnTop) {
124 SetWindowStyle(GetWindowStyle() | wxSTAY_ON_TOP);
125 LOG_INFO(
"Preferences",
"Window set to always on top");
130 int minW = screen.x * windowSettings.minWindowWidthPct / 100;
131 int minH = screen.y * windowSettings.minWindowHeightPct / 100;
132 SetMinSize(wxSize(minW, minH));
133 LOG_INFO(
"Preferences",
"Minimum window size set to " + std::to_string(minW) +
"x" + std::to_string(minH) +
" (" +
134 std::to_string(windowSettings.minWindowWidthPct) +
"% x " +
135 std::to_string(windowSettings.minWindowHeightPct) +
"% of screen)");
137 if (windowSettings.maxWindowWidthPct > 0 && windowSettings.maxWindowHeightPct > 0) {
138 int maxW = screen.x * windowSettings.maxWindowWidthPct / 100;
139 int maxH = screen.y * windowSettings.maxWindowHeightPct / 100;
140 SetMaxSize(wxSize(maxW, maxH));
141 LOG_INFO(
"Preferences",
"Maximum window size set to " + std::to_string(maxW) +
"x" + std::to_string(maxH) +
142 " (" + std::to_string(windowSettings.maxWindowWidthPct) +
"% x " +
143 std::to_string(windowSettings.maxWindowHeightPct) +
"% of screen)");
147 if (windowSettings.enforceAspectRatio) {
150 LOG_INFO(
"Preferences",
"Aspect ratio enforcement requested: " + std::to_string(windowSettings.aspectRatio) +
151 " (requires custom implementation)");
155 const auto &mainPanelSettings = prefs.GetMainPanelSettings();
158 wxColour bgColor(mainPanelSettings.canvasBackgroundColor.r, mainPanelSettings.canvasBackgroundColor.g,
159 mainPanelSettings.canvasBackgroundColor.b);
160 m_scenePanel->ApplyCanvasBackgroundColor(bgColor);
162 LOG_INFO(
"Preferences",
"Main Panel settings applied on startup");
168 LOG_INFO(
"Performance",
"Performance monitoring initialized in MainFrame");
171 const auto &parserSettings = prefs.GetParserSettings();
172 if (parserSettings.autoLoadLastFile && !parserSettings.lastOpenedFilePath.empty()) {
173 wxString lastFilePath = wxString::FromUTF8(parserSettings.lastOpenedFilePath);
174 if (wxFileExists(lastFilePath)) {
175 LOG_INFO(
"Preferences",
"Auto-loading last opened file: " + parserSettings.lastOpenedFilePath);
176 CallAfter([
this, lastFilePath]() { LoadXMLFile(lastFilePath,
false); });
178 LOG_WARNING(
"Preferences",
"Last opened file no longer exists: " + parserSettings.lastOpenedFilePath);
184 m_scenePanel->SetWelcomeActionCallback([
this](
const std::string &action,
const std::string ¶m) {
185 if (action ==
"new_project") {
187 }
else if (action ==
"open_project") {
189 }
else if (action ==
"open_recent") {
190 OpenProject(wxString::FromUTF8(param));
191 }
else if (action ==
"docs") {
192 wxLaunchDefaultBrowser(
"https://cloudflare-docs.ember-docs.pages.dev/");
202 LOG_INFO(
"MainFrame",
"Saved left panel state");
207 LOG_INFO(
"MainFrame",
"Saved right panel state");
212 LOG_INFO(
"MainFrame",
"Saved bottom panel state");
220 wxAuiPaneInfo &leftPaneInfo =
m_auiManager->GetPane(
"left_panel");
221 wxAuiPaneInfo &rightPaneInfo =
m_auiManager->GetPane(
"right_panel");
222 wxAuiPaneInfo &bottomPaneInfo =
m_auiManager->GetPane(
"bottom_panel");
224 auto &leftSettings = prefs.GetLeftPanelSettings();
225 auto &rightSettings = prefs.GetRightPanelSettings();
226 auto &bottomSettings = prefs.GetBottomPanelSettings();
228 leftSettings.lastPanelVisible = leftPaneInfo.IsShown();
229 rightSettings.lastPanelVisible = rightPaneInfo.IsShown();
230 bottomSettings.lastPanelVisible = bottomPaneInfo.IsShown();
232 LOG_INFO(
"MainFrame", wxString::Format(
"Saved panel visibility - left: %d, right: %d, bottom: %d",
233 leftSettings.lastPanelVisible, rightSettings.lastPanelVisible,
234 bottomSettings.lastPanelVisible)
241 if (prefManager.GetPreferences().SaveToFile(configPath)) {
242 LOG_INFO(
"MainFrame",
"Saved preferences to disk: " + configPath);
244 LOG_ERROR(
"MainFrame",
"Failed to save preferences to disk");
252 LOG_INFO(
"AUI",
"Cleaned up unified AUI manager");
259 auto &prefs = prefsMgr.GetPreferences();
260 const auto &windowSettings = prefs.GetWindowSettings();
261 const auto &mainPanelSettings = prefs.GetMainPanelSettings();
262 const auto &btViewSettings = prefs.GetBehaviorTreeViewSettings();
265 wxMenu *menuFile =
new wxMenu;
268 wxString newProjectLabel = wxString::Format(
"New &Project...\t%s", windowSettings.newProjectHotkey);
269 menuFile->Append(
ID_NewProject, newProjectLabel,
"Create a new BehaviorTree project");
271 wxString openProjectLabel = wxString::Format(
"Open Pro&ject...\t%s", windowSettings.openProjectHotkey);
272 menuFile->Append(
ID_OpenProject, openProjectLabel,
"Open an existing BehaviorTree project");
274 menuFile->Append(
ID_CloseProject,
"&Close Project",
"Close the current project");
275 menuFile->Append(
ID_SaveProject,
"Save Project",
"Save the current project");
277 menuFile->AppendSeparator();
280 wxString openFileLabel = wxString::Format(
"&Open XML File...\t%s", windowSettings.openFileHotkey);
281 menuFile->Append(
ID_LoadXML, openFileLabel,
"Load a behavior tree from XML file");
282 menuFile->AppendSeparator();
284 wxString saveLabel = wxString::Format(
"&Save\t%s", windowSettings.saveHotkey);
285 menuFile->Append(
ID_SaveXML, saveLabel,
"Save current behavior tree to XML file");
287 wxString saveAsLabel = wxString::Format(
"Save &As...\t%s", windowSettings.saveAsHotkey);
288 menuFile->Append(
ID_SaveAsXML, saveAsLabel,
"Save behavior tree to new XML file");
289 menuFile->AppendSeparator();
290 menuFile->Append(wxID_EXIT);
292 wxMenu *menuHelp =
new wxMenu;
293 menuHelp->Append(wxID_ABOUT);
296 wxMenu *menuView =
new wxMenu;
298 wxString maximizeLabel = wxString::Format(
"&Maximize\t%s", windowSettings.maximizeHotkey);
301 menuView->AppendSeparator();
304 wxString nextSceneLabel = wxString::Format(
"&Next Scene\t%s", mainPanelSettings.nextSceneHotkey);
305 menuView->Append(
ID_NextScene, nextSceneLabel,
"Switch to next scene");
307 wxString prevSceneLabel = wxString::Format(
"&Previous Scene\t%s", mainPanelSettings.previousSceneHotkey);
308 menuView->Append(
ID_PreviousScene, prevSceneLabel,
"Switch to previous scene");
310 menuView->AppendSeparator();
313 wxString resetViewLabel = wxString::Format(
"&Reset View\t%s", btViewSettings.resetViewHotkey);
314 menuView->Append(
ID_ResetZoom, resetViewLabel,
"Reset zoom and pan to default");
316 menuView->AppendSeparator();
319 wxString resetUILabel = wxString::Format(
"Reset &UI\t%s", windowSettings.resetUIHotkey);
320 menuView->Append(
ID_ResetUI, resetUILabel,
"Reset all panels and preferences to defaults");
323 wxMenu *menuSettings =
new wxMenu;
325 wxString preferencesLabel = wxString::Format(
"&Preferences...\t%s", windowSettings.preferencesHotkey);
327 "Open the preferences dialog to configure EmberForge settings");
329 wxString parserConfigLabel = wxString::Format(
"&Parser Configuration...\t%s", windowSettings.parserConfigHotkey);
331 "Configure XML parser behavior and create custom profiles");
333 wxMenuBar *menuBar =
new wxMenuBar;
334 menuBar->Append(menuFile,
"&File");
335 menuBar->Append(menuView,
"&View");
336 menuBar->Append(menuSettings,
"&Settings");
337 menuBar->Append(menuHelp,
"&Help");
343 SetStatusText(
"Welcome to EmberForge!");
360 wxBitmap leftNormal(resourcePath +
"left_panel/Left_Panel_Normal_22.png", wxBITMAP_TYPE_PNG);
361 wxBitmap leftHovered(resourcePath +
"left_panel/Left_Panel_Hovered_22.png", wxBITMAP_TYPE_PNG);
362 wxBitmap leftPressed(resourcePath +
"left_panel/Left_Panel_Pressed_22.png", wxBITMAP_TYPE_PNG);
363 wxBitmap leftDisabled(resourcePath +
"left_panel/Left_Panel_Disabled_22.png", wxBITMAP_TYPE_PNG);
366 wxDefaultPosition, wxSize(32, 32), wxBORDER_NONE | wxBU_EXACTFIT);
367 leftBtn->SetBitmapHover(leftHovered);
368 leftBtn->SetBitmapPressed(leftPressed);
369 leftBtn->SetBitmapDisabled(leftDisabled);
370 leftBtn->SetToolTip(
"Toggle Left Panel");
374 wxBitmap bottomNormal(resourcePath +
"bottom_panel/Bottom_Panel_Normal_22.png", wxBITMAP_TYPE_PNG);
375 wxBitmap bottomHovered(resourcePath +
"bottom_panel/Bottom_Panel_Hovered_22.png", wxBITMAP_TYPE_PNG);
376 wxBitmap bottomPressed(resourcePath +
"bottom_panel/Bottom_Panel_Pressed_22.png", wxBITMAP_TYPE_PNG);
377 wxBitmap bottomDisabled(resourcePath +
"bottom_panel/Bottom_Panel_Disabled_22.png", wxBITMAP_TYPE_PNG);
380 wxDefaultPosition, wxSize(32, 32), wxBORDER_NONE | wxBU_EXACTFIT);
381 bottomBtn->SetBitmapHover(bottomHovered);
382 bottomBtn->SetBitmapPressed(bottomPressed);
383 bottomBtn->SetBitmapDisabled(bottomDisabled);
384 bottomBtn->SetToolTip(
"Toggle Bottom Panel");
388 wxBitmap rightNormal(resourcePath +
"right_panel/Right_Panel_Normal_22.png", wxBITMAP_TYPE_PNG);
389 wxBitmap rightHovered(resourcePath +
"right_panel/Right_Panel_Hovered_22.png", wxBITMAP_TYPE_PNG);
390 wxBitmap rightPressed(resourcePath +
"right_panel/Right_Panel_Pressed_22.png", wxBITMAP_TYPE_PNG);
391 wxBitmap rightDisabled(resourcePath +
"right_panel/Right_Panel_Disabled_22.png", wxBITMAP_TYPE_PNG);
394 wxDefaultPosition, wxSize(32, 32), wxBORDER_NONE | wxBU_EXACTFIT);
395 rightBtn->SetBitmapHover(rightHovered);
396 rightBtn->SetBitmapPressed(rightPressed);
397 rightBtn->SetBitmapDisabled(rightDisabled);
398 rightBtn->SetToolTip(
"Toggle Right Panel");
403 LOG_INFO(
"UI",
"Panel toggle toolbar created with state-aware custom icons");
408 LOG_INFO(
"Config",
"Configuration system initialized - use Settings > Preferences to configure");
414 auto &prefs = prefsMgr.GetPreferences();
415 const auto &windowSettings = prefs.GetWindowSettings();
416 const auto &mainPanelSettings = prefs.GetMainPanelSettings();
417 const auto &btViewSettings = prefs.GetBehaviorTreeViewSettings();
419 wxMenuBar *menuBar = GetMenuBar();
424 int fileMenuIndex = menuBar->FindMenu(
"File");
425 if (fileMenuIndex != wxNOT_FOUND) {
426 wxMenu *menuFile = menuBar->GetMenu(fileMenuIndex);
428 wxMenuItem *newProjectItem = menuFile->FindItem(
ID_NewProject);
429 if (newProjectItem) {
430 newProjectItem->SetItemLabel(wxString::Format(
"New &Project...\t%s", windowSettings.newProjectHotkey));
434 if (openProjectItem) {
435 openProjectItem->SetItemLabel(
436 wxString::Format(
"Open Pro&ject...\t%s", windowSettings.openProjectHotkey));
439 wxMenuItem *openFileItem = menuFile->FindItem(
ID_LoadXML);
441 openFileItem->SetItemLabel(wxString::Format(
"&Open XML File...\t%s", windowSettings.openFileHotkey));
444 wxMenuItem *saveItem = menuFile->FindItem(
ID_SaveXML);
446 saveItem->SetItemLabel(wxString::Format(
"&Save\t%s", windowSettings.saveHotkey));
449 wxMenuItem *saveAsItem = menuFile->FindItem(
ID_SaveAsXML);
451 saveAsItem->SetItemLabel(wxString::Format(
"Save &As...\t%s", windowSettings.saveAsHotkey));
457 int viewMenuIndex = menuBar->FindMenu(
"View");
458 if (viewMenuIndex != wxNOT_FOUND) {
459 wxMenu *menuView = menuBar->GetMenu(viewMenuIndex);
463 maximizeItem->SetItemLabel(wxString::Format(
"&Maximize\t%s", windowSettings.maximizeHotkey));
466 wxMenuItem *nextSceneItem = menuView->FindItem(
ID_NextScene);
468 nextSceneItem->SetItemLabel(wxString::Format(
"&Next Scene\t%s", mainPanelSettings.nextSceneHotkey));
473 prevSceneItem->SetItemLabel(
474 wxString::Format(
"&Previous Scene\t%s", mainPanelSettings.previousSceneHotkey));
477 wxMenuItem *resetViewItem = menuView->FindItem(
ID_ResetZoom);
479 resetViewItem->SetItemLabel(wxString::Format(
"&Reset View\t%s", btViewSettings.resetViewHotkey));
482 wxMenuItem *resetUIItem = menuView->FindItem(
ID_ResetUI);
484 resetUIItem->SetItemLabel(wxString::Format(
"Reset &UI\t%s", windowSettings.resetUIHotkey));
490 int settingsMenuIndex = menuBar->FindMenu(
"Settings");
491 if (settingsMenuIndex != wxNOT_FOUND) {
492 wxMenu *menuSettings = menuBar->GetMenu(settingsMenuIndex);
494 wxMenuItem *preferencesItem = menuSettings->FindItem(
ID_Preferences);
495 if (preferencesItem) {
496 preferencesItem->SetItemLabel(
497 wxString::Format(
"&Preferences...\t%s", windowSettings.preferencesHotkey));
500 wxMenuItem *parserConfigItem = menuSettings->FindItem(
ID_ParserConfig);
501 if (parserConfigItem) {
502 parserConfigItem->SetItemLabel(
503 wxString::Format(
"&Parser Configuration...\t%s", windowSettings.parserConfigHotkey));
508 LOG_INFO(
"Preferences",
"Menu hotkeys refreshed");
517 if (!activeScene || activeScene->GetSceneType() !=
"BehaviorTree")
526 auto currentTree = treeVis->GetTree();
528 treeVis->SetTree(currentTree);
531 treeVis->RefreshCanvas();
550 if (windowSettings.showStatusBar) {
551 GetStatusBar()->Show();
553 GetStatusBar()->Hide();
558 wxAuiPaneInfo &leftPane =
m_auiManager->GetPane(
"left_panel");
559 if (leftPane.IsOk()) {
560 leftPane.CaptionVisible(windowSettings.showPanelCaptions);
563 wxAuiPaneInfo &rightPane =
m_auiManager->GetPane(
"right_panel");
564 if (rightPane.IsOk()) {
565 rightPane.CaptionVisible(windowSettings.showPanelCaptions);
568 wxAuiPaneInfo &bottomPane =
m_auiManager->GetPane(
"bottom_panel");
569 if (bottomPane.IsOk()) {
570 bottomPane.CaptionVisible(windowSettings.showPanelCaptions);
573 wxAuiPaneInfo &scenePane =
m_auiManager->GetPane(
"scene_panel");
574 if (scenePane.IsOk()) {
575 scenePane.CaptionVisible(windowSettings.showPanelCaptions);
580 std::string(
"Panel captions ") + (windowSettings.showPanelCaptions ?
"shown" :
"hidden"));
584 long currentStyle = GetWindowStyle();
585 bool styleChanged =
false;
587 if (windowSettings.alwaysOnTop) {
588 if (!(currentStyle & wxSTAY_ON_TOP)) {
589 SetWindowStyle(currentStyle | wxSTAY_ON_TOP);
590 LOG_INFO(
"Preferences",
"Window set to always on top");
594 if (currentStyle & wxSTAY_ON_TOP) {
595 SetWindowStyle(currentStyle & ~wxSTAY_ON_TOP);
596 LOG_INFO(
"Preferences",
"Window always on top disabled");
604 wxMenuBar *menuBar = GetMenuBar();
614 const auto &mainPanelSettings = prefs.GetMainPanelSettings();
617 wxColour bgColor(mainPanelSettings.canvasBackgroundColor.r, mainPanelSettings.canvasBackgroundColor.g,
618 mainPanelSettings.canvasBackgroundColor.b);
623 for (
int i = 0; i < sceneCount; i++) {
630 treeVis->LoadPreferences();
632 wxString::Format(
"Reloaded zoom/pan preferences for scene %d", i).ToStdString());
638 LOG_INFO(
"Preferences",
"Main Panel settings applied");
644 LOG_INFO(
"Preferences",
"Preferences refreshed and applied to UI");
658 if (!newScene || newScene->
GetSceneType() !=
"BehaviorTree")
699 const auto &rightSettings = prefs.GetRightPanelSettings();
700 const auto &bottomSettings = prefs.GetBottomPanelSettings();
703 bool showLeftPanel =
true;
704 switch (leftSettings.panelStartupState) {
706 showLeftPanel =
true;
709 showLeftPanel =
false;
712 showLeftPanel = leftSettings.lastPanelVisible;
717 const auto &windowSettings = prefs.GetWindowSettings();
720 wxSize cs = GetClientSize();
723 int leftW = cs.x * leftSettings.defaultPanelWidthPct / 100;
724 int leftMinW = cs.x * leftSettings.minimumPanelWidthPct / 100;
729 .Caption(
"Left Panel")
730 .CaptionVisible(windowSettings.showPanelCaptions)
732 .BestSize(wxSize(leftW, -1))
733 .MinSize(wxSize(leftMinW, -1))
737 .Resizable(leftSettings.allowPanelCollapse)
738 .Show(showLeftPanel));
742 .Caption(
"Main Panel")
744 .CaptionVisible(windowSettings.showPanelCaptions)
751 bool showRightPanel =
true;
752 switch (rightSettings.panelStartupState) {
754 showRightPanel =
true;
757 showRightPanel =
false;
760 showRightPanel = rightSettings.lastPanelVisible;
764 int rightW = cs.x * rightSettings.defaultPanelWidthPct / 100;
765 int rightMinW = cs.x * rightSettings.minimumPanelWidthPct / 100;
769 .Caption(
"Right Panel")
770 .CaptionVisible(windowSettings.showPanelCaptions)
772 .BestSize(wxSize(rightW, -1))
773 .MinSize(wxSize(rightMinW, -1))
777 .Resizable(rightSettings.allowPanelCollapse)
778 .Show(showRightPanel));
781 bool showBottomPanel =
true;
782 switch (bottomSettings.panelStartupState) {
784 showBottomPanel =
true;
787 showBottomPanel =
false;
790 showBottomPanel = bottomSettings.lastPanelVisible;
794 int bottomH = cs.y * bottomSettings.defaultPanelHeightPct / 100;
795 int bottomMinH = cs.y * bottomSettings.minimumPanelHeightPct / 100;
798 .Name(
"bottom_panel")
799 .Caption(
"Bottom Panel")
800 .CaptionVisible(windowSettings.showPanelCaptions)
802 .BestSize(wxSize(-1, bottomH))
803 .MinSize(wxSize(-1, bottomMinH))
807 .Resizable(bottomSettings.allowPanelCollapse)
808 .Show(showBottomPanel));
811 double leftProp = leftSettings.defaultPanelWidthPct / 100.0;
812 double leftMinProp = leftSettings.minimumPanelWidthPct / 100.0;
813 double rightProp = rightSettings.defaultPanelWidthPct / 100.0;
814 double rightMinProp = rightSettings.minimumPanelWidthPct / 100.0;
815 double bottomProp = bottomSettings.defaultPanelHeightPct / 100.0;
816 double bottomMinProp = bottomSettings.minimumPanelHeightPct / 100.0;
818 m_layout.Track(
"left_panel", leftProp, leftMinProp,
true);
819 m_layout.Track(
"right_panel", rightProp, rightMinProp,
true);
820 m_layout.Track(
"bottom_panel", bottomProp, bottomMinProp,
false);
832 for (
size_t i = 0; i < notebook->GetPageCount(); ++i) {
833 auto *page = notebook->GetPage(i);
834 auto *tab =
dynamic_cast<ITab *
>(page);
835 if (tab && tab->GetTabType() ==
"Navigator") {
839 LOG_INFO(
"MainFrame",
"Found navigator tab reference");
846 for (
size_t i = 0; i < rightNb->GetPageCount(); ++i) {
847 auto *page = rightNb->GetPage(i);
848 auto *tab =
dynamic_cast<ITab *
>(page);
849 if (tab && tab->GetTabType() ==
"Navigator") {
853 LOG_INFO(
"MainFrame",
"Found navigator tab reference in right panel");
865 for (
size_t i = 0; i < rightNotebook->GetPageCount(); ++i) {
866 auto *page = rightNotebook->GetPage(i);
867 auto *tab =
dynamic_cast<ITab *
>(page);
868 if (tab && tab->GetTabType() ==
"Properties") {
870 LOG_INFO(
"MainFrame",
"Found properties tab reference");
886 wxBitmapButton *leftBtn =
dynamic_cast<wxBitmapButton *
>(leftWin);
890 wxBitmap(resourcePath +
"left_panel/Left_Panel_Pressed_22.png", wxBITMAP_TYPE_PNG));
893 wxBitmap(resourcePath +
"left_panel/Left_Panel_Normal_22.png", wxBITMAP_TYPE_PNG));
901 wxBitmapButton *bottomBtn =
dynamic_cast<wxBitmapButton *
>(bottomWin);
903 if (showBottomPanel) {
904 bottomBtn->SetBitmap(
905 wxBitmap(resourcePath +
"bottom_panel/Bottom_Panel_Pressed_22.png", wxBITMAP_TYPE_PNG));
907 bottomBtn->SetBitmap(
908 wxBitmap(resourcePath +
"bottom_panel/Bottom_Panel_Normal_22.png", wxBITMAP_TYPE_PNG));
916 wxBitmapButton *rightBtn =
dynamic_cast<wxBitmapButton *
>(rightWin);
918 if (showRightPanel) {
920 wxBitmap(resourcePath +
"right_panel/Right_Panel_Pressed_22.png", wxBITMAP_TYPE_PNG));
923 wxBitmap(resourcePath +
"right_panel/Right_Panel_Normal_22.png", wxBITMAP_TYPE_PNG));
930 "Created unified AUI layout - left panel: %d%% (min %d%%), visible=%d; right panel: "
931 "%d%% (min %d%%), visible=%d; bottom panel: %d%% (min %d%%), visible=%d",
932 leftSettings.defaultPanelWidthPct, leftSettings.minimumPanelWidthPct, showLeftPanel,
933 rightSettings.defaultPanelWidthPct, rightSettings.minimumPanelWidthPct, showRightPanel,
934 bottomSettings.defaultPanelHeightPct, bottomSettings.minimumPanelHeightPct, showBottomPanel)
947 static int idleCount = 0;
948 if (++idleCount % 100 == 0) {
958 wxSize newSize = GetClientSize();
972 wxMessageBox(
"This is a refactored wxWidgets application for EmberForge\n\n"
973 "Now with better code organization using separate panel classes!",
974 "About EmberForge", wxOK | wxICON_INFORMATION);
978 wxFileDialog openFileDialog(
this,
"Open Behavior Tree",
"",
"",
"XML files (*.xml)|*.xml|All files (*.*)|*.*",
979 wxFD_OPEN | wxFD_FILE_MUST_EXIST);
981 if (openFileDialog.ShowModal() == wxID_CANCEL)
984 wxString filePath = openFileDialog.GetPath();
985 wxString ext = wxFileName(filePath).GetExt().Lower();
989 wxString message = wxString::Format(
"Unsupported File Format\n\n"
992 "EmberForge currently only supports opening XML behavior tree files.\n"
993 "Please select an XML file (.xml) to open.",
994 wxFileName(filePath).GetFullName(), ext.IsEmpty() ?
"none" : ext);
996 wxMessageBox(message,
"Unsupported File Format", wxOK | wxICON_WARNING,
this);
997 LOG_WARNING(
"FileOpen", std::string(
"Attempted to open unsupported file type: .") + ext.ToStdString());
1005 LOG_INFO(
"Parser", std::string(
"Loading XML file: ") + filePath.ToStdString());
1008 bool shouldRename =
false;
1009 wxString newSceneName;
1026 (void)confirmOverride;
1029 LoadingDialog loadingDialog(
this,
"Loading Behavior Tree (libxml2)");
1031 loadingDialog.Show(
true);
1032 loadingDialog.
AppendLog(
"Selected file: " + filePath,
"[FILE]");
1041 explicit ProgressCallback(
LoadingDialog *dialog) : dialog_(dialog) {}
1043 bool OnProgress(
const EmberCore::String &message,
int current,
int total)
override {
1046 bool shouldContinue = dialog_->
UpdateProgress(current, total, message);
1051 return shouldContinue;
1057 void AppendLog(
const wxString &message,
const wxString &prefix =
"") {
1064 ProgressCallback progressCallback(&loadingDialog);
1068 auto activeProfile = configMgr.GetActiveProfile();
1076 progressCallback.AppendLog(
"libxml2 parser initialized",
"[INIT]");
1077 progressCallback.AppendLog(
"Starting file parsing with libxml2...",
"[INFO]");
1080 auto behaviorTree = parser.
ParseFromFile(filePath.ToStdString());
1085 loadingDialog.
AppendLog(
"libxml2 parsing completed successfully",
"[COMPLETE]");
1092 if (!behaviorTree) {
1094 const auto &errors = parser.
GetErrors();
1095 for (
const auto &error : errors) {
1096 loadingDialog.
AppendLog(wxString::FromUTF8(error.message),
"[ERROR]");
1100 loadingDialog.ShowModal();
1102 wxString errorMessage =
"Failed to parse XML file with libxml2:\n\n";
1103 for (
const auto &error : errors) {
1104 errorMessage +=
"• " + error.message +
"\n";
1107 wxMessageBox(errorMessage,
"libxml2 XML Parsing Error", wxOK | wxICON_ERROR);
1108 LOG_ERROR(
"Parser",
"libxml2 XML parsing failed");
1113 loadingDialog.ShowModal();
1115 LOG_INFO(
"Parser",
"libxml2 XML parsed successfully, loading into scene view");
1118 if (behaviorTree->HasRootNode()) {
1123 wxMessageBox(
"Cannot load behavior tree: The main tree appears to be empty.\n\n"
1124 "This usually happens when the tree contains only SubTree nodes,\n"
1125 "which are not yet implemented in the parser.",
1126 "Empty Behavior Tree", wxOK | wxICON_WARNING);
1127 LOG_WARNING(
"Parser",
"Refusing to load empty behavior tree - root node '" + rootNode->
GetName() +
1128 "' has no children");
1142 auto directAdapter = std::make_shared<EmberCore::DirectTreeAdapter>(behaviorTree);
1143 treeVis->SetBehaviorTree(directAdapter);
1146 if (
m_navigatorTab && directAdapter && directAdapter->HasRootNode()) {
1147 wxFileName fn(filePath);
1148 m_navigatorTab->SetActiveTree(directAdapter, fn.GetName().ToStdString());
1151 if (
auto *visualization = treeVis->GetTreeVisualization()) {
1152 visualization->RefreshCanvas();
1153 visualization->Refresh();
1154 visualization->Update();
1156 if (directAdapter && directAdapter->HasRootNode()) {
1157 CallAfter([visualization, directAdapter]() {
1158 visualization->CenterOnNode(directAdapter->GetRootNode());
1159 LOG_INFO(
"Parser",
"Centered view on root node after loading XML (UniParser)");
1177 LOG_INFO(
"Preferences",
"Saved last opened file path: " + filePath.ToStdString());
1182 wxFileName fileName(filePath);
1183 wxString sceneName = fileName.GetName();
1191 LOG_INFO(
"Parser",
"Behavior tree loaded into scene view");
1192 SetStatusText(
"Loaded with libxml2: " + wxFileName(filePath).GetName());
1194 LOG_ERROR(
"Parser",
"Active scene is not a behavior tree scene");
1195 wxMessageBox(
"Active scene is not a behavior tree scene",
"Error", wxOK | wxICON_ERROR);
1198 LOG_ERROR(
"Parser",
"No active scene available");
1199 wxMessageBox(
"No active scene available",
"Error", wxOK | wxICON_ERROR);
1202 LOG_ERROR(
"Parser",
"Scene panel not available");
1203 wxMessageBox(
"Scene panel not available",
"Error", wxOK | wxICON_ERROR);
1209 LOG_ERROR(
"Parser",
"Scene panel not available");
1210 wxMessageBox(
"Scene panel not available",
"Error", wxOK | wxICON_ERROR);
1215 wxFileName fileName(filePath);
1216 wxString sceneName = fileName.GetName();
1219 LOG_INFO(
"Parser", std::string(
"Creating new scene: ") + sceneName.ToStdString());
1220 int sceneIndex =
m_scenePanel->CreateNewScene(sceneName);
1222 if (sceneIndex < 0) {
1223 LOG_ERROR(
"Parser",
"Failed to create new scene (limit may have been reached)");
1245 wxFileDialog saveFileDialog(
this,
"Save Behavior Tree XML",
"",
"",
"XML files (*.xml)|*.xml",
1246 wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
1248 if (saveFileDialog.ShowModal() == wxID_CANCEL)
1251 wxString filePath = saveFileDialog.GetPath();
1256 LOG_INFO(
"Serializer",
"Saving behavior tree to: " + filePath.ToStdString());
1260 if (!behaviorTreeScene) {
1261 wxMessageBox(
"No behavior tree scene is currently active",
"Save Error", wxOK | wxICON_ERROR);
1262 LOG_ERROR(
"Serializer",
"No behavior tree scene is active");
1268 if (!treeStructure) {
1269 wxMessageBox(
"No behavior tree is currently loaded",
"Save Error", wxOK | wxICON_ERROR);
1270 LOG_ERROR(
"Serializer",
"No behavior tree is loaded");
1277 wxMessageBox(
"Unexpected tree adapter type",
"Save Error", wxOK | wxICON_ERROR);
1278 LOG_ERROR(
"Serializer",
"Tree adapter is not a DirectTreeAdapter");
1283 std::shared_ptr<EmberCore::BehaviorTree> tree = adapter->
GetTree();
1285 wxMessageBox(
"Failed to access behavior tree data",
"Save Error", wxOK | wxICON_ERROR);
1286 LOG_ERROR(
"Serializer",
"Failed to get tree from adapter");
1292 auto activeProfile = configMgr.GetActiveProfile();
1297 auto validation = validator.
Validate(tree.get());
1300 bool hasIssues = validation.
HasErrors() || validation.HasWarnings();
1305 if (validation.HasErrors()) {
1306 msg +=
"⚠️ VALIDATION ERRORS (development mode allows saving):\n\n";
1307 for (
const auto &issue : validation.issues) {
1309 wxString path = wxString::FromUTF8(issue.node_path.c_str());
1310 wxString message = wxString::FromUTF8(issue.message.c_str());
1311 if (!path.empty()) {
1312 msg +=
"• [" + path +
"] " + message +
"\n";
1314 msg +=
"• " + message +
"\n";
1316 LOG_WARNING(
"Serializer",
"Validation error: " + issue.message);
1323 if (validation.HasWarnings()) {
1324 msg +=
"ℹ️ VALIDATION WARNINGS:\n\n";
1325 for (
const auto &issue : validation.issues) {
1327 wxString path = wxString::FromUTF8(issue.node_path.c_str());
1328 wxString message = wxString::FromUTF8(issue.message.c_str());
1329 if (!path.empty()) {
1330 msg +=
"• [" + path +
"] " + message +
"\n";
1332 msg +=
"• " + message +
"\n";
1334 LOG_INFO(
"Serializer",
"Validation warning: " + issue.message);
1340 msg +=
"These issues may cause problems when loading the file.\n";
1341 msg +=
"Save anyway? (Development workflow)";
1343 wxString title = validation.HasErrors() ?
"Validation Issues" :
"Validation Warnings";
1344 int result = wxMessageBox(msg, title, wxYES_NO | wxICON_WARNING);
1345 if (result != wxYES) {
1346 LOG_INFO(
"Serializer",
"Save cancelled by user due to validation issues");
1356 SetStatusText(
"Saved: " + wxFileName(filePath).GetName());
1357 LOG_INFO(
"Serializer",
"Tree saved successfully to: " + filePath.ToStdString());
1359 wxMessageBox(
"Behavior tree saved successfully!",
"Save Complete", wxOK | wxICON_INFORMATION);
1362 wxString errorMessage =
"Failed to save XML file:\n\n";
1363 const auto &errors = serializer.
GetErrors();
1364 for (
const auto &error : errors) {
1365 errorMessage +=
"• " + wxString::FromUTF8(error.message.c_str()) +
"\n";
1368 wxMessageBox(errorMessage,
"Save Error", wxOK | wxICON_ERROR);
1369 LOG_ERROR(
"Serializer",
"Failed to save tree to: " + filePath.ToStdString());
1376 int result = dialog.ShowModal();
1378 if (result == wxID_OK) {
1379 LOG_INFO(
"Config",
"Preferences dialog closed with OK - settings applied");
1380 SetStatusText(
"Preferences updated");
1385 LOG_INFO(
"Config",
"Preferences dialog cancelled");
1386 SetStatusText(
"Preferences cancelled");
1394 wxAuiPaneInfo &pane =
m_auiManager->GetPane(
"left_panel");
1396 pane.Show(!pane.IsShown());
1400 wxBitmapButton *btn =
dynamic_cast<wxBitmapButton *
>(
event.GetEventObject());
1403 if (pane.IsShown()) {
1404 btn->SetBitmap(wxBitmap(resourcePath +
"Left_Panel_Pressed_22.png", wxBITMAP_TYPE_PNG));
1406 btn->SetBitmap(wxBitmap(resourcePath +
"Left_Panel_Normal_22.png", wxBITMAP_TYPE_PNG));
1410 LOG_INFO(
"UI", std::string(
"Left panel ") + (pane.IsShown() ?
"shown" :
"hidden"));
1411 SetStatusText(pane.IsShown() ?
"Left panel shown" :
"Left panel hidden");
1419 wxAuiPaneInfo &pane =
m_auiManager->GetPane(
"bottom_panel");
1421 pane.Show(!pane.IsShown());
1425 wxBitmapButton *btn =
dynamic_cast<wxBitmapButton *
>(
event.GetEventObject());
1428 if (pane.IsShown()) {
1429 btn->SetBitmap(wxBitmap(resourcePath +
"Bottom_Panel_Pressed_22.png", wxBITMAP_TYPE_PNG));
1431 btn->SetBitmap(wxBitmap(resourcePath +
"Bottom_Panel_Normal_22.png", wxBITMAP_TYPE_PNG));
1435 LOG_INFO(
"UI", std::string(
"Bottom panel ") + (pane.IsShown() ?
"shown" :
"hidden"));
1436 SetStatusText(pane.IsShown() ?
"Bottom panel shown" :
"Bottom panel hidden");
1444 wxAuiPaneInfo &pane =
m_auiManager->GetPane(
"right_panel");
1446 pane.Show(!pane.IsShown());
1450 wxBitmapButton *btn =
dynamic_cast<wxBitmapButton *
>(
event.GetEventObject());
1453 if (pane.IsShown()) {
1454 btn->SetBitmap(wxBitmap(resourcePath +
"Right_Panel_Pressed_22.png", wxBITMAP_TYPE_PNG));
1456 btn->SetBitmap(wxBitmap(resourcePath +
"Right_Panel_Normal_22.png", wxBITMAP_TYPE_PNG));
1460 LOG_INFO(
"UI", std::string(
"Right panel ") + (pane.IsShown() ?
"shown" :
"hidden"));
1461 SetStatusText(pane.IsShown() ?
"Right panel shown" :
"Right panel hidden");
1472 wxAuiPaneInfo &leftPane =
m_auiManager->GetPane(
"left_panel");
1474 if (leftWin && leftPane.IsOk()) {
1475 wxBitmapButton *leftBtn =
dynamic_cast<wxBitmapButton *
>(leftWin);
1477 if (leftPane.IsShown()) {
1478 leftBtn->SetBitmap(wxBitmap(resourcePath +
"left_panel/Left_Panel_Pressed_22.png", wxBITMAP_TYPE_PNG));
1480 leftBtn->SetBitmap(wxBitmap(resourcePath +
"left_panel/Left_Panel_Normal_22.png", wxBITMAP_TYPE_PNG));
1486 wxAuiPaneInfo &bottomPane =
m_auiManager->GetPane(
"bottom_panel");
1488 if (bottomWin && bottomPane.IsOk()) {
1489 wxBitmapButton *bottomBtn =
dynamic_cast<wxBitmapButton *
>(bottomWin);
1491 if (bottomPane.IsShown()) {
1492 bottomBtn->SetBitmap(
1493 wxBitmap(resourcePath +
"bottom_panel/Bottom_Panel_Pressed_22.png", wxBITMAP_TYPE_PNG));
1495 bottomBtn->SetBitmap(
1496 wxBitmap(resourcePath +
"bottom_panel/Bottom_Panel_Normal_22.png", wxBITMAP_TYPE_PNG));
1502 wxAuiPaneInfo &rightPane =
m_auiManager->GetPane(
"right_panel");
1504 if (rightWin && rightPane.IsOk()) {
1505 wxBitmapButton *rightBtn =
dynamic_cast<wxBitmapButton *
>(rightWin);
1507 if (rightPane.IsShown()) {
1508 rightBtn->SetBitmap(
1509 wxBitmap(resourcePath +
"right_panel/Right_Panel_Pressed_22.png", wxBITMAP_TYPE_PNG));
1511 rightBtn->SetBitmap(
1512 wxBitmap(resourcePath +
"right_panel/Right_Panel_Normal_22.png", wxBITMAP_TYPE_PNG));
1534 int result = dialog.ShowModal();
1536 if (result == wxID_OK) {
1537 LOG_INFO(
"Config",
"Parser configuration updated");
1538 SetStatusText(
"Parser configuration saved");
1540 LOG_INFO(
"Config",
"Parser configuration cancelled");
1541 SetStatusText(
"Parser configuration cancelled");
1548 LOG_INFO(
"Tool",
"Editor Tool activated - Opening editor for BT configuration");
1554 LOG_INFO(
"Tool",
"Monitor Tool activated - Opening performance monitor");
1560 LOG_INFO(
"Tool",
"Log Replay Tool activated - Replaying performance logs");
1567 bool isMaximized = IsMaximized();
1570 LOG_INFO(
"View",
"Maximized window");
1573 LOG_INFO(
"View",
"Restored window to normal size");
1602 int zoomPercent =
static_cast<int>(defaultZoom * 100);
1604 SetStatusText(wxString::Format(
"View reset (zoom: %d%%)", zoomPercent));
1605 LOG_INFO(
"View",
"View reset to default (zoom + pan)");
1613 int result = wxMessageBox(
"This will:\n"
1614 "- Close the current project (if open)\n"
1615 "- Reset all preferences to defaults\n"
1616 "- Reset all panels to their default tabs\n\n"
1617 "Are you sure you want to reset the UI?",
1618 "Reset UI", wxYES_NO | wxICON_WARNING,
this);
1620 if (result != wxYES) {
1624 LOG_INFO(
"UI",
"Resetting UI to defaults");
1629 LOG_INFO(
"UI",
"Closed active project");
1635 prefsMgr.GetPreferences() = defaultPrefs;
1638 prefsMgr.GetPreferences().SaveToFile(configPath);
1639 LOG_INFO(
"Preferences",
"Reset preferences to defaults and saved to: " + configPath);
1644 wxAuiPaneInfo &leftPane =
m_auiManager->GetPane(
"left_panel");
1645 if (leftPane.IsOk() && !leftPane.IsShown()) {
1649 wxAuiPaneInfo &rightPane =
m_auiManager->GetPane(
"right_panel");
1650 if (rightPane.IsOk() && !rightPane.IsShown()) {
1654 wxAuiPaneInfo &bottomPane =
m_auiManager->GetPane(
"bottom_panel");
1655 if (bottomPane.IsOk() && !bottomPane.IsShown()) {
1697 SetStatusText(
"UI reset to defaults");
1698 LOG_INFO(
"UI",
"UI reset complete");
1703 wxDateTime now = wxDateTime::Now();
1704 wxString timestamp = now.Format(
"%Y-%m-%d %H:%M:%S");
1705 wxString logEntry = wxString::Format(
"%s %s\n", timestamp, message);
1716 if (!config.GetSyncSettings().enableHierarchyToScene) {
1717 if (config.GetDebugSettings().enableSyncLogging) {
1718 LOG_INFO(
"Sync",
"Hierarchy → Scene: Disabled by configuration");
1725 if (config.GetDebugSettings().enableSyncLogging) {
1726 LOG_INFO(
"Sync",
"Hierarchy → Scene: Skipped (already updating from scene)");
1732 if (config.GetDebugSettings().enableSyncLogging) {
1733 LOG_INFO(
"Sync",
"Hierarchy → Scene: Node selected in hierarchy tab");
1741 LOG_ERROR(
"Sync",
"Scene panel is null");
1748 if (!activeScene || activeScene->
GetSceneType() !=
"BehaviorTree") {
1749 LOG_ERROR(
"Sync",
"No active behavior tree scene");
1757 LOG_ERROR(
"Sync",
"Failed to cast to BehaviorTreeScene");
1764 LOG_ERROR(
"Sync",
"Tree visualization is null");
1770 wxPanel *scenePanel = btScene->
GetPanel();
1771 if (!scenePanel || !scenePanel->IsShown()) {
1772 LOG_ERROR(
"Sync",
"Scene panel is not shown");
1780 if (config.GetSyncSettings().enableSelectionHighlight) {
1781 if (config.GetDebugSettings().enableSyncLogging) {
1782 LOG_INFO(
"Sync",
"Hierarchy → Scene: Calling treeVis->SetSelectedNode()");
1784 treeVis->SetSelectedNode(selectedNode);
1794 if (config.GetDebugSettings().enableSyncLogging) {
1795 LOG_INFO(
"Sync",
"Hierarchy → Properties: Updating properties with node: " + legacyNode->
GetName());
1800 if (config.GetDebugSettings().enableSyncLogging) {
1801 LOG_INFO(
"Sync",
"Hierarchy → Properties: Clearing properties selection");
1807 if (config.GetDebugSettings().enableSyncLogging) {
1808 LOG_INFO(
"Sync",
"Hierarchy → Scene: Sync operations completed");
1818 if (!config.GetSyncSettings().enableSceneToHierarchy) {
1819 if (config.GetDebugSettings().enableSyncLogging) {
1820 LOG_INFO(
"Sync",
"Scene → Hierarchy: Disabled by configuration");
1827 if (config.GetDebugSettings().enableSyncLogging) {
1828 LOG_INFO(
"Sync",
"Scene → Hierarchy: Skipped (already updating from hierarchy)");
1834 if (config.GetDebugSettings().enableSyncLogging) {
1835 LOG_INFO(
"Sync",
"Scene → Hierarchy: Node selected in scene view");
1856 legacyNode = nodeAdapter->GetWrappedNode();
1860 LOG_INFO(
"Sync",
"Scene → Properties: Updating properties with node: " + legacyNode->
GetName());
1863 LOG_INFO(
"Sync",
"Scene → Properties: Clearing properties selection");
1873 LOG_INFO(
"MainFrame",
"PropertiesTab is being closed, nullifying pointer");
1892 if (activeScene && activeScene->
GetSceneType() ==
"BehaviorTree") {
1895 auto adapter = btScene->GetTreeAdapter();
1903 LOG_INFO(
"MainFrame",
"Navigator tab reference updated and wired");
1909 for (
size_t i = 0; i <
m_scenePanel->GetSceneCount(); ++i) {
1920 LOG_INFO(
"MainFrame",
"NavigatorTab is being closed, nullifying pointer");
1931 if (!activeScene || activeScene->
GetSceneType() !=
"BehaviorTree") {
1948 return std::shared_ptr<EmberCore::Node>(rawTree, [](
EmberCore::Node *) {});
1957 if (activeScene && activeScene->
GetSceneType() ==
"BehaviorTree") {
1969 LOG_INFO(
"MainFrame",
"Set selected node on newly created properties tab");
1986 if (!activeScene || activeScene->
GetSceneType() !=
"BehaviorTree") {
2002 wxPanel *scenePanel = btScene->
GetPanel();
2003 if (!scenePanel || !scenePanel->IsShown()) {
2008 treeVis->CenterOnNode(node);
2016 if (!activeScene || activeScene->
GetSceneType() !=
"BehaviorTree")
2025 treeVis->InvalidateLayout();
2037 static bool updating =
false;
2039 LOG_INFO(
"Sync",
"SyncSelection: Prevented infinite loop");
2044 LOG_INFO(
"Sync",
"SyncSelection: Synchronizing selection to: " + (selectedNode ? selectedNode->
GetName() :
"null"));
2057 LOG_INFO(
"Sync",
"SyncSelection: Updating properties with node: " + legacyNode->
GetName());
2061 LOG_INFO(
"Sync",
"SyncSelection: Clearing properties selection");
2069 if (activeScene && activeScene->
GetSceneType() ==
"BehaviorTree") {
2072 LOG_INFO(
"Sync",
"SyncSelection: Updating scene view");
2079 LOG_INFO(
"Sync",
"SyncSelection: Synchronization completed");
2095 LOG_INFO(
"Menu",
"OnProjectSettings called");
2100 LOG_INFO(
"Project",
"Creating new project...");
2103 int result = dialog.ShowModal();
2105 if (result == wxID_OK) {
2112 if (!wxFileName::DirExists(defaultProjectsDir)) {
2113 wxFileName::Mkdir(defaultProjectsDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL);
2117 wxFileDialog saveDialog(
this,
"Save BehaviorTree Project", defaultProjectsDir,
2118 wxString::FromUTF8(project->GetName()) +
".btproj",
2119 "BehaviorTree Project (*.btproj)|*.btproj", wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
2121 if (saveDialog.ShowModal() == wxID_OK) {
2122 wxString projectPath = saveDialog.GetPath();
2123 project->SetProjectFilePath(projectPath.ToStdString());
2125 if (project->SaveToFile(projectPath.ToStdString())) {
2130 projectManager.AddToRecentProjects(projectPath.ToStdString());
2135 LOG_INFO(
"Project",
"Project created and saved: " + projectPath.ToStdString());
2136 SetStatusText(
"Project created: " + wxString::FromUTF8(project->GetName()));
2138 wxMessageBox(
"Failed to save project file.",
"Error", wxOK | wxICON_ERROR,
this);
2139 LOG_ERROR(
"Project",
"Failed to save project file");
2147 LOG_INFO(
"Project",
"Opening project dialog...");
2150 int result = dialog.ShowModal();
2158 LOG_INFO(
"Project",
"Opening project: " + projectPath.ToStdString());
2161 auto project = std::make_shared<EmberCore::BehaviorTreeProject>();
2162 if (!project->LoadFromFile(projectPath.ToStdString())) {
2163 wxMessageBox(
"Failed to load project file.",
"Error", wxOK | wxICON_ERROR,
this);
2164 LOG_ERROR(
"Project",
"Failed to load project from: " + projectPath.ToStdString());
2177 projectManager.AddToRecentProjects(projectPath.ToStdString());
2181 auto profile = configManager.GetProfile(project->GetParserProfileName());
2183 profile = configManager.GetActiveProfile();
2190 LoadingDialog loadingDialog(
this,
"Loading Project: " + wxString::FromUTF8(project->GetName()));
2191 loadingDialog.Show();
2200 explicit ProjectProgressCallback(
LoadingDialog *dialog) : dialog_(dialog) {}
2202 bool OnProgress(
const EmberCore::String &message,
int current,
int total)
override {
2204 bool shouldContinue = dialog_->
UpdateProgress(current, total, message);
2206 return shouldContinue;
2212 ProjectProgressCallback progressCallback(&loadingDialog);
2216 loadingDialog.
AppendLog(
"Project: " + wxString::FromUTF8(project->GetName()),
"[INFO]");
2217 loadingDialog.
AppendLog(
"Parser Profile: " + wxString::FromUTF8(profile->GetName()),
"[INFO]");
2218 loadingDialog.
AppendLog(
"Resource Files: " + wxString::Format(
"%zu", project->GetResourceCount()),
"[INFO]");
2222 const auto &resources = project->GetResources();
2223 for (
size_t i = 0; i < resources.size(); ++i) {
2224 wxFileName fn(wxString::FromUTF8(project->ResolveResourcePath(resources[i])));
2225 loadingDialog.
AppendLog(wxString::Format(
" %zu. %s", i + 1, fn.GetFullName()),
"[FILE]");
2228 loadingDialog.
AppendLog(
"Starting parser...",
"[INIT]");
2236 loadingDialog.
AppendLog(
"=== Per-File Parsing Results ===",
"[INFO]");
2239 int totalBlackboards = 0;
2240 int totalWarnings = 0;
2241 int totalErrors = 0;
2243 for (
const auto &fileInfo : parseResult.file_infos) {
2244 wxFileName fn(wxString::FromUTF8(fileInfo.filepath));
2245 wxString filename = fn.GetFullName();
2247 if (fileInfo.parsed_successfully) {
2248 loadingDialog.
AppendLog(filename +
" - Parsed successfully",
"[SUCCESS]");
2250 loadingDialog.
AppendLog(filename +
" - Parse FAILED",
"[ERROR]");
2254 if (fileInfo.tree_count > 0) {
2256 for (
size_t i = 0; i < fileInfo.tree_ids.size(); ++i) {
2259 treeList += wxString::FromUTF8(fileInfo.tree_ids[i]);
2261 loadingDialog.
AppendLog(wxString::Format(
" Trees: %d (%s)", fileInfo.tree_count, treeList),
"[INFO]");
2262 totalTrees += fileInfo.tree_count;
2266 if (fileInfo.blackboard_count > 0) {
2268 for (
size_t i = 0; i < fileInfo.blackboard_ids.size(); ++i) {
2271 bbList += wxString::FromUTF8(fileInfo.blackboard_ids[i]);
2273 loadingDialog.
AppendLog(wxString::Format(
" Blackboards: %d (%s)", fileInfo.blackboard_count, bbList),
2275 totalBlackboards += fileInfo.blackboard_count;
2279 if (!fileInfo.subtree_refs.empty()) {
2281 for (
size_t i = 0; i < fileInfo.subtree_refs.size(); ++i) {
2284 refList += wxString::FromUTF8(fileInfo.subtree_refs[i]);
2286 loadingDialog.
AppendLog(
" SubTree refs: " + refList,
"[INFO]");
2290 for (
const auto &warning : fileInfo.warnings) {
2291 loadingDialog.
AppendLog(
" " + wxString::FromUTF8(warning),
"[WARN]");
2296 for (
const auto &error : fileInfo.errors) {
2297 loadingDialog.
AppendLog(
" " + wxString::FromUTF8(error),
"[ERROR]");
2303 if (!parseResult.unimplemented_references.empty()) {
2305 loadingDialog.
AppendLog(
"=== Unimplemented Tree References ===",
"[WARN]");
2306 for (
const auto &ref : parseResult.unimplemented_references) {
2307 loadingDialog.
AppendLog(
" " + wxString::FromUTF8(ref),
"[WARN]");
2311 if (!parseResult.duplicate_tree_ids.empty()) {
2313 loadingDialog.
AppendLog(
"=== Duplicate Tree IDs ===",
"[WARN]");
2314 for (
const auto &dup : parseResult.duplicate_tree_ids) {
2315 loadingDialog.
AppendLog(
" " + wxString::FromUTF8(dup),
"[WARN]");
2319 if (!parseResult.duplicate_blackboard_ids.empty()) {
2321 loadingDialog.
AppendLog(
"=== Duplicate Blackboard IDs ===",
"[WARN]");
2322 for (
const auto &dup : parseResult.duplicate_blackboard_ids) {
2323 loadingDialog.
AppendLog(
" " + wxString::FromUTF8(dup),
"[WARN]");
2327 if (!parseResult.unresolved_blackboard_includes.empty()) {
2329 loadingDialog.
AppendLog(
"=== Unresolved Blackboard Includes ===",
"[WARN]");
2330 for (
const auto &unres : parseResult.unresolved_blackboard_includes) {
2331 loadingDialog.
AppendLog(
" " + wxString::FromUTF8(unres),
"[WARN]");
2337 loadingDialog.
AppendLog(
"=== Summary ===",
"[INFO]");
2338 loadingDialog.
AppendLog(wxString::Format(
"Files parsed: %zu", parseResult.file_infos.size()),
"[INFO]");
2339 loadingDialog.
AppendLog(wxString::Format(
"Total trees: %d", totalTrees),
"[INFO]");
2340 loadingDialog.
AppendLog(wxString::Format(
"Total blackboards: %d", totalBlackboards),
"[INFO]");
2341 if (totalWarnings > 0) {
2342 loadingDialog.
AppendLog(wxString::Format(
"Warnings: %d", totalWarnings),
"[WARN]");
2344 if (totalErrors > 0) {
2345 loadingDialog.
AppendLog(wxString::Format(
"Errors: %d", totalErrors),
"[ERROR]");
2350 if (parseResult.success) {
2352 project->SetTreeImplementationStatus(parseResult.tree_statuses);
2364 auto bbScene = std::make_unique<BlackboardScene>(
m_scenePanel->GetNotebook());
2376 bool hasMainTreeEntryPoint = !parseResult.main_tree_name.empty() &&
2379 if (hasMainTreeEntryPoint) {
2381 LOG_INFO(
"Project",
"Found main tree entry point: " + parseResult.main_tree_name);
2382 loadingDialog.
AppendLog(
"Main tree: " + wxString::FromUTF8(parseResult.main_tree_name),
"[INFO]");
2389 LOG_INFO(
"Project",
"No main tree entry point found - project has " +
2391 " trees. Use tree selector to view individual trees.");
2392 loadingDialog.
AppendLog(
"No main tree - loading first tree",
"[INFO]");
2400 LOG_INFO(
"Project",
"No main tree entry point found - creating project overview with all trees");
2401 loadingDialog.
AppendLog(
"No main tree - creating project overview",
"[INFO]");
2413 loadingDialog.
AppendLog(
"Project loaded successfully!",
"[COMPLETE]");
2414 loadingDialog.
UpdateProgress(100, 100,
"Project loaded successfully");
2419 LOG_INFO(
"Project",
"Project loaded successfully: " + project->GetName() +
" with " +
2421 SetStatusText(
"Project: " + wxString::FromUTF8(project->GetName()) +
" (" +
2424 loadingDialog.ShowModal();
2427 loadingDialog.
AppendLog(
"Project parsing failed",
"[ERROR]");
2429 wxString errorMsg =
"Failed to parse project files:\n\n";
2430 for (
const auto &err : parseResult.errors) {
2431 errorMsg +=
"- " + wxString::FromUTF8(err.message) +
"\n";
2435 for (
const auto &err : parseResult.errors) {
2436 loadingDialog.
AppendLog(wxString::FromUTF8(err.message),
"[ERROR]");
2441 LOG_ERROR(
"Project",
"Failed to parse project files");
2444 loadingDialog.ShowModal();
2446 wxMessageBox(errorMsg,
"Error", wxOK | wxICON_ERROR,
this);
2456 LOG_WARNING(
"MainFrame",
"Tree not found: " + treeId);
2469 if (scene && scene->
GetSceneType() ==
"BehaviorTree") {
2474 int idx =
m_scenePanel->CreateNewScene(wxString::FromUTF8(treeId));
2478 if (scene && scene->
GetSceneType() ==
"BehaviorTree") {
2495 std::shared_ptr<EmberCore::Node> rootNode(tree, rootNodePtr);
2505 SetStatusText(
"Viewing tree: " + wxString::FromUTF8(treeId));
2506 LOG_INFO(
"MainFrame",
"Loaded tree into scene: " + treeId);
2514 return scene && scene->
GetSceneType() ==
"BehaviorTree";
2519 LOG_WARNING(
"MainFrame",
"Tree not found: " + treeId);
2524 LOG_ERROR(
"MainFrame",
"Scene panel not available");
2532 int sceneIdx =
m_scenePanel->CreateNewScene(wxString::FromUTF8(treeId));
2546 std::shared_ptr<EmberCore::Node> rootNode(tree, rootNodePtr);
2559 SetStatusText(
"Viewing tree: " + wxString::FromUTF8(treeId));
2560 LOG_INFO(
"MainFrame",
"Opened tree '" + treeId +
"' in new scene");
2568 LOG_INFO(
"MainFrame",
"Creating project overview scene with " + std::to_string(
m_projectTrees.size()) +
" trees");
2573 if (scene && scene->
GetSceneType() ==
"BehaviorTree") {
2578 int idx =
m_scenePanel->CreateNewScene(
"Project Overview");
2596 wxScrolled<wxPanel> *overviewPanel =
new wxScrolled<wxPanel>(btScene->
GetPanel(), wxID_ANY);
2597 overviewPanel->SetBackgroundColour(wxColour(45, 45, 50));
2598 overviewPanel->SetScrollRate(20, 20);
2601 wxBoxSizer *horizontalSizer =
new wxBoxSizer(wxHORIZONTAL);
2604 size_t treeIndex = 0;
2606 const std::string &treeId = pair.first;
2607 auto tree = pair.second;
2615 wxPanel *treePanel =
new wxPanel(overviewPanel, wxID_ANY);
2616 treePanel->SetBackgroundColour(wxColour(45, 45, 50));
2617 wxBoxSizer *treeSizer =
new wxBoxSizer(wxVERTICAL);
2620 wxStaticText *label =
new wxStaticText(treePanel, wxID_ANY, wxString::FromUTF8(treeId));
2621 wxFont labelFont = label->GetFont();
2622 labelFont.SetPointSize(labelFont.GetPointSize() + 2);
2623 labelFont.MakeBold();
2624 label->SetFont(labelFont);
2625 label->SetForegroundColour(wxColour(200, 200, 200));
2626 treeSizer->Add(label, 0, wxALL | wxALIGN_CENTER, 10);
2629 vis->SetMinSize(wxSize(600, 400));
2631 auto behaviorTree = std::make_shared<EmberCore::BehaviorTree>(treeId);
2632 std::shared_ptr<EmberCore::Node> rootNode(tree, rootNodePtr);
2633 behaviorTree->SetRootNode(rootNode);
2634 auto treeAdapter = std::make_shared<EmberCore::DirectTreeAdapter>(behaviorTree);
2635 vis->SetTree(treeAdapter);
2637 treeSizer->Add(vis, 1, wxEXPAND | wxALL, 5);
2638 treePanel->SetSizer(treeSizer);
2641 horizontalSizer->Add(treePanel, 0, wxEXPAND | wxALL, 10);
2645 wxPanel *separator =
new wxPanel(overviewPanel, wxID_ANY, wxDefaultPosition, wxSize(2, -1));
2646 separator->SetBackgroundColour(wxColour(80, 80, 85));
2647 horizontalSizer->Add(separator, 0, wxEXPAND | wxTOP | wxBOTTOM, 10);
2653 overviewPanel->SetSizer(horizontalSizer);
2654 overviewPanel->FitInside();
2657 wxSizer *panelSizer = btScene->
GetPanel()->GetSizer();
2659 panelSizer->Clear(
false);
2660 panelSizer->Add(overviewPanel, 1, wxEXPAND | wxALL, 5);
2664 SetStatusText(
"Project Overview: " + wxString::Format(
"%zu trees",
m_projectTrees.size()));
2665 LOG_INFO(
"MainFrame",
"Project overview scene created successfully");
2677 LOG_INFO(
"Project",
"No project to close");
2688 if (scene && scene->
GetSceneType() ==
"BehaviorTree") {
2695 treeViz->SetTree(
nullptr);
2696 treeViz->RefreshCanvas();
2714 for (
size_t i = 0; i <
m_scenePanel->GetSceneCount(); ++i) {
2739 SetStatusText(
"Project closed");
2744 wxMessageBox(
"No project is currently open.",
"Information", wxOK | wxICON_INFORMATION,
this);
2748 wxString projectPath = wxString::FromUTF8(
m_activeProject->GetProjectFilePath());
2750 if (projectPath.IsEmpty()) {
2752 wxFileDialog saveDialog(
this,
"Save BehaviorTree Project",
"",
2754 "BehaviorTree Project (*.btproj)|*.btproj", wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
2756 if (saveDialog.ShowModal() != wxID_OK) {
2760 projectPath = saveDialog.GetPath();
2764 LOG_INFO(
"Project",
"Project saved: " + projectPath.ToStdString());
2765 SetStatusText(
"Project saved");
2767 wxMessageBox(
"Failed to save project.",
"Error", wxOK | wxICON_ERROR,
this);
2768 LOG_ERROR(
"Project",
"Failed to save project");
2774 wxMessageBox(
"No project is currently open.",
"Information", wxOK | wxICON_INFORMATION,
this);
2779 int result = dialog.ShowModal();
2781 if (result == wxID_OK) {
2784 LOG_INFO(
"Project",
"Project settings updated");
2785 SetStatusText(
"Project settings saved");
BehaviorTreeProjectDialog::OnProjectNameChanged BehaviorTreeProjectDialog::OnRemoveFiles wxEND_EVENT_TABLE() BehaviorTreeProjectDialog
#define LOG_ERROR(category, message)
#define LOG_WARNING(category, message)
#define LOG_INFO(category, message)
MainFrame::OnExit MainFrame::OnNewProject MainFrame::OnCloseProject MainFrame::OnToggleMaximize MainFrame::OnPreviousScene MainFrame::OnPreferences MainFrame::OnEditorTool EVT_BUTTON(ID_MonitorTool, MainFrame::OnMonitorTool) EVT_PAINT(MainFrame
wxBEGIN_EVENT_TABLE(MainFrame, wxFrame) EVT_MENU(wxID_EXIT
MainFrame::OnExit EVT_MENU(wxID_ABOUT, MainFrame::OnAbout) EVT_MENU(ID_NewProject
Centralized resource path management for EmberForge.
Dialog for creating and configuring BehaviorTree projects.
std::shared_ptr< EmberCore::BehaviorTreeProject > GetProject() const
Get the configured project (after dialog is closed with OK)
Scene implementation for behavior tree visualization.
EmberCore::ITreeStructure * GetAbstractedTree() const
Get the abstracted tree structure.
wxPanel * GetPanel() override
Returns the wxPanel used as the scene content.
EmberCore::Node * GetBehaviorTree() const
Get the behavior tree root node (legacy interface)
EmberForge::ForgeTreeCanvas * GetTreeVisualization() const
void SetNodeSelectionCallback(NodeSelectionCallback callback)
Set callback for node selection events.
std::shared_ptr< EmberCore::ITreeStructure > GetTreeAdapter() const
void SetBehaviorTree(std::shared_ptr< EmberCore::Node > root)
Set the behavior tree for this scene using legacy Node interface.
Bottom dockable panel for logs and output.
BehaviorTreeConfig & GetConfig()
static BehaviorTreeConfigManager & GetInstance()
Unified validation system for behavior trees.
ValidationResult Validate(const BehaviorTree *tree) const
Validate a behavior tree against the parser profile.
static ConfigManager & GetInstance()
Direct tree adapter that wraps BehaviorTree for unified tree operations.
std::shared_ptr< BehaviorTree > GetTree() const
Callback interface for reporting parsing progress.
Abstract interface for tree nodes that can be visualized.
virtual size_t GetId() const =0
virtual const String & GetName() const =0
Thread-safe XML parser using libxml2 for behavior tree files.
void SetProgressCallback(IParseProgressCallback *callback)
const std::vector< ParseError > & GetErrors() const
ProjectParseResult ParseProject(BehaviorTreeProject *project)
std::shared_ptr< BehaviorTree > ParseFromFile(const EmberCore::String &filepath)
XML serializer using libxml2 for behavior tree files.
const std::vector< SerializeError > & GetErrors() const
bool SerializeToFile(std::shared_ptr< BehaviorTree > tree, const EmberCore::String &filepath)
Serialize behavior tree to file.
Adapter class that wraps the current Node implementation to work with ITreeNode interface.
Represents a node in a behavior tree structure.
const String & GetName() const
size_t GetChildCount() const
static ParserConfig CreateDefault()
static ProjectManager & GetInstance()
AppPreferences & GetPreferences()
static AppPreferencesManager & GetInstance()
Application preferences configuration.
LeftPanelSettings & GetLeftPanelSettings()
bool SaveToFile(const EmberCore::String &filename) const
MainPanelSettings & GetMainPanelSettings()
ParserSettings & GetParserSettings()
WindowSettings & GetWindowSettings()
static EmberCore::String GetDefaultConfigPath()
static wxString GetDir(const wxString &relativeDir)
Get the full path to a resource directory.
void ResetView()
Resets zoom and pan to default values.
void SetSelectedNode(EmberCore::ITreeNode *node)
Sets the selected node.
Interface for scene-based UI components (e.g., views or screens).
virtual wxString GetSceneType() const =0
Returns the scene type identifier.
Interface for tab-based UI components in the application.
Left sidebar panel specialized for left-side functionality.
Custom loading dialog with detailed progress information.
void MarkComplete()
Mark loading as complete and change button to "Close".
void SetMaxProgress(int max)
Set the maximum value for the progress bar.
bool UpdateProgress(int current, int max, const wxString &message=wxEmptyString)
Update progress and optionally add a log message.
bool WasCancelled() const
Check if the user requested cancellation.
void AppendLog(const wxString &message, const wxString &prefix="")
Add a log message without updating progress.
Main application window for EmberForge.
std::string m_currentTreeId
void SyncSelection(EmberCore::ITreeNode *selectedNode)
virtual ~MainFrame()
Destructor - cleans up AUI manager and panels.
int CountTabGlobally(const wxString &tabName) const
void LogMessage(const wxString &message)
void UpdateTreeSelector()
bool m_isUpdatingFromHierarchy
void OnSaveProject(wxCommandEvent &event)
void OnPreferences(wxCommandEvent &event)
void OnToggleLeftPanel(wxCommandEvent &event)
bool IsActiveSceneBehaviorTree() const
void UpdatePropertiesTabReference(PropertiesTab *propertiesTab)
void OnPaint(wxPaintEvent &event)
void OnParserConfig(wxCommandEvent &event)
void NavigateToBlackboard(const std::string &bbId)
void OnCloseProject(wxCommandEvent &event)
std::shared_ptr< EmberCore::BehaviorTreeProject > GetActiveProject() const
void OnPreviousScene(wxCommandEvent &event)
void OnNewProject(wxCommandEvent &event)
void OnToggleMaximize(wxCommandEvent &event)
void RefreshPreferences()
BlackboardScene * m_blackboardScene
void OpenTreeInNewScene(const std::string &treeId)
void CreateProjectOverviewScene()
void UpdateNavigatorTabReference(ForgeNavigatorTab *navigatorTab)
bool m_isUpdatingFromScene
void OnSaveAsXML(wxCommandEvent &event)
void OnMonitorTool(wxCommandEvent &event)
void OnIdle(wxIdleEvent &event)
void RefreshHierarchyTree()
void OnToggleRightPanel(wxCommandEvent &event)
LeftSidePanel * m_leftPanel
void CreateMenuAndStatusBar()
void OnHierarchySelectionChanged(EmberCore::ITreeNode *selectedNode)
void CenterOnNode(EmberCore::ITreeNode *node)
void ShowProjectSettings()
void OnOpenProject(wxCommandEvent &event)
RightSidePanel * m_rightPanel
void OnFrameResize(wxSizeEvent &event)
void DoSaveXML(const wxString &filePath)
void OnPropertiesTabClosed()
void OnLogReplayTool(wxCommandEvent &event)
void OnResetZoom(wxCommandEvent &event)
void OnToggleBottomPanel(wxCommandEvent &event)
void LoadTreeIntoScene(const std::string &treeId)
void LoadXMLFile(const wxString &filePath, bool confirmOverride=false)
std::shared_ptr< EmberCore::BehaviorTreeProject > m_activeProject
void OnNextScene(wxCommandEvent &event)
void OnAbout(wxCommandEvent &event)
wxToolBar * m_panelToggleToolbar
void OnNavigatorTabClosed()
void OnSaveXML(wxCommandEvent &event)
BottomPanel * m_bottomPanel
ForgeNavigatorTab * m_navigatorTab
std::string m_mainTreeName
void OnLoadXML(wxCommandEvent &event)
void OnSceneSelectionChanged(EmberCore::ITreeNode *selectedNode)
std::map< std::string, std::shared_ptr< EmberCore::BehaviorTree > > m_projectTrees
bool HasActiveProject() const
void OnEditorTool(wxCommandEvent &event)
wxString m_currentFilePath
void OnResetUI(wxCommandEvent &event)
PropertiesTab * m_propertiesTab
void OnProjectSettings(wxCommandEvent &event)
void InitializeConfigurationMenu()
void UpdatePanelToggleButtons()
std::map< std::string, std::vector< std::string > > m_blackboardIncludesMap
void InvalidateCanvasLayout()
std::map< std::string, std::shared_ptr< EmberCore::Blackboard > > m_projectBlackboards
void RefreshMenuHotkeys()
std::shared_ptr< EmberCore::Node > GetSharedBehaviorTree() const
void RefreshCanvasVisualization()
void LoadXMLFileInNewScene(const wxString &filePath)
void OnExit(wxCommandEvent &event)
void CreatePanelToggleToolbar()
EmberUI::ProportionalLayout m_layout
wxAuiManager * m_auiManager
Central panel for managing and displaying behavior tree scenes.
Comprehensive dialog for managing parser configuration profiles.
Preferences dialog for configuring EmberForge application settings.
Dialog for opening and managing BehaviorTree projects.
wxString GetSelectedProjectPath() const
Get the selected project path (after dialog is closed with OK)
bool HasSelectedProject() const
Check if a project was selected.
Right sidebar panel specialized for right-side functionality.
std::string String
Framework-agnostic string type.
wxSize GetScreenSize()
Returns the primary display size in pixels.
EmberCore::String lastOpenedFilePath