31 std::erase_if(m_Windows, [](
const std::shared_ptr<BasicEditorWindow>& window) {
32 return !window->isOpen;
35 static enum { ACTION_NONE, ACTION_QUIT, ACTION_TO_SELECTOR, ACTION_RUN_DEBUG, ACTION_RUN_RELEASE } pendingQuitAction = ACTION_NONE;
36 bool openSavePopup =
false;
38 if (ImGui::BeginMenuBar()) {
39 if (ImGui::BeginMenu(
"File")) {
40 if (ImGui::MenuItem(
"New Scene")) {
43 if (ImGui::MenuItem(
"Open Scene")) {
46 if (ImGui::MenuItem(
"Save Scene")) {
47 std::string sceneName =
vex::GetAssetPath(m_editor.getSceneManager()->getLastSceneName());
48 m_editor.getSceneManager()->GetScene(sceneName)->Save(sceneName);
50 if (ImGui::MenuItem(
"Save Scene As")) {
53 if (ImGui::MenuItem(
"Quit to Project Selector")) {
54 pendingQuitAction = ACTION_TO_SELECTOR;
57 if (ImGui::MenuItem(
"Quit Project")) {
58 pendingQuitAction = ACTION_QUIT;
63 if (ImGui::BeginMenu(
"Build")) {
64 if (ImGui::MenuItem(
"Build Debug")) {
67 if (ImGui::MenuItem(
"Build Release")) {
70 if (ImGui::MenuItem(
"Build Distribution (Shipping)")) {
75 if (ImGui::BeginMenu(
"Preferences")) {
76 if (ImGui::MenuItem(
"Editor Settings")) {
79 if (ImGui::MenuItem(
"Project Settings")) {
84 if (ImGui::BeginMenu(
"Window")) {
85 if (ImGui::MenuItem(
"Console")){
86 m_editor.ShowConsole();
91 float runMenuWidth = ImGui::CalcTextSize(
"Run").x + 40.0f;
92 float currentPos = ImGui::GetCursorPosX();
93 float rightPos = ImGui::GetWindowWidth() - runMenuWidth;
95 if (rightPos > currentPos) {
96 ImGui::SetCursorPosX(rightPos);
99 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1.00f, 0.23f, 0.01f, 1.0f));
100 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.47f, 0.05f, 0.05f, 1.0f));
101 ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.30f, 0.03f, 0.03f, 1.0f));
102 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.94f, 0.85f, 0.85f, 1.0f));
104 ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0.0f);
106 if (ImGui::Button(
"Run", ImVec2(runMenuWidth - 10, 0))) {
107 ImGui::OpenPopup(
"RunPopup");
110 ImGui::PopStyleVar();
111 ImGui::PopStyleColor(4);
113 if (ImGui::BeginPopup(
"RunPopup")) {
114 if (ImGui::MenuItem(
"Debug")) {
115 pendingQuitAction = ACTION_RUN_DEBUG;
116 openSavePopup =
true;
118 if (ImGui::MenuItem(
"Release")) {
119 pendingQuitAction = ACTION_RUN_RELEASE;
120 openSavePopup =
true;
128 ImGui::OpenPopup(
"Save Changes?");
131 ImVec2 center = ImGui::GetMainViewport()->GetCenter();
132 ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
134 if (ImGui::BeginPopupModal(
"Save Changes?",
nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
135 ImGui::Text(
"Do you want to save changes to the current scene before quitting?");
139 if (ImGui::Button(
"Save", ImVec2(120, 0))) {
140 std::string sceneName =
vex::GetAssetPath(m_editor.getSceneManager()->getLastSceneName());
141 m_editor.getSceneManager()->GetScene(sceneName)->Save(sceneName);
142 ImGui::CloseCurrentPopup();
144 if (pendingQuitAction == ACTION_TO_SELECTOR) {
147 }
else if (pendingQuitAction == ACTION_QUIT) {
149 }
else if (pendingQuitAction == ACTION_RUN_DEBUG) {
151 }
else if (pendingQuitAction == ACTION_RUN_RELEASE) {
154 pendingQuitAction = ACTION_NONE;
159 if (ImGui::Button(
"Don't Save", ImVec2(120, 0))) {
160 ImGui::CloseCurrentPopup();
162 if (pendingQuitAction == ACTION_TO_SELECTOR) {
165 }
else if (pendingQuitAction == ACTION_QUIT) {
167 }
else if (pendingQuitAction == ACTION_RUN_DEBUG) {
169 }
else if (pendingQuitAction == ACTION_RUN_RELEASE) {
172 pendingQuitAction = ACTION_NONE;
177 if (ImGui::Button(
"Cancel", ImVec2(120, 0))) {
178 ImGui::CloseCurrentPopup();
179 pendingQuitAction = ACTION_NONE;
188 std::filesystem::path selectorPath = binDir /
"VexProjectSelector";
191 selectorPath +=
".exe";
194 if (!std::filesystem::exists(selectorPath)) {
195 vex::log(
"Error: Could not find ProjectSelector at %s", selectorPath.string().c_str());
199 std::string pathStr = selectorPath.string();
202 ShellExecuteA(NULL,
"open", pathStr.c_str(), NULL, NULL, SW_SHOW);
210 execl(pathStr.c_str(), pathStr.c_str(), (
char*)NULL);
217 std::shared_ptr<BasicEditorWindow> openSceneWindow = std::make_shared<BasicEditorWindow>();
218 std::weak_ptr<BasicEditorWindow> weakWindow = openSceneWindow;
222 openSceneWindow->Create = [
this, weakWindow, editorProperties](
vex::ImGUIWrapper& wrapper){
223 wrapper.addUIFunction([=,
this](){
224 auto window = weakWindow.lock();
if (!window || !window->isOpen)
return;
225 ImGui::SetNextWindowSize(ImVec2(600, 400), ImGuiCond_FirstUseEver);
227 if (ImGui::Begin(
"Editor Settings", &window->isOpen)) {
228 ImReflect::Input(
"##data", editorProperties);
234 m_Windows.push_back(openSceneWindow);
235 openSceneWindow->Create(m_ImGUIWrapper);
239 std::shared_ptr<BasicEditorWindow> openSceneWindow = std::make_shared<BasicEditorWindow>();
240 std::weak_ptr<BasicEditorWindow> weakWindow = openSceneWindow;
244 openSceneWindow->Create = [
this, weakWindow, projectProperties](
vex::ImGUIWrapper& wrapper){
245 wrapper.addUIFunction([=,
this](){
246 auto window = weakWindow.lock();
if (!window || !window->isOpen)
return;
247 ImGui::SetNextWindowSize(ImVec2(600, 400), ImGuiCond_FirstUseEver);
249 if (ImGui::Begin(
"Project Settings", &window->isOpen)) {
250 ImReflect::Input(
"##data", projectProperties);
256 m_Windows.push_back(openSceneWindow);
257 openSceneWindow->Create(m_ImGUIWrapper);
261 std::shared_ptr<BasicEditorWindow> openSceneWindow = std::make_shared<BasicEditorWindow>();
262 std::weak_ptr<BasicEditorWindow> weakWindow = openSceneWindow;
263 std::string startPath = m_editor.getProjectBinaryPath() +
"/Assets";
264 auto dialogBrowser = std::make_shared<vex::AssetBrowser>(startPath);
265 auto showError = std::make_shared<bool>(
false);
267 openSceneWindow->Create = [
this, weakWindow, dialogBrowser, showError](
vex::ImGUIWrapper& wrapper){
268 wrapper.addUIFunction([=,
this](){
269 auto window = weakWindow.lock();
if (!window || !window->isOpen)
return;
270 ImGui::SetNextWindowSize(ImVec2(600, 400), ImGuiCond_FirstUseEver);
272 if (ImGui::Begin(
"Open Scene File", &window->isOpen)) {
273 std::string selectedFile = dialogBrowser->Draw(m_editor.GetEditorIcons(), m_editor.getEditorProperties()->assetBrowserThumbnailSize);
274 if (!selectedFile.empty() && !dialogBrowser->GetExtension(selectedFile).empty()) {
276 if (dialogBrowser->GetExtension(selectedFile) ==
".json") {
278 int type = dialogBrowser->GetJSONAssetType(selectedFile);
280 vex::log(
"Menu Bar requesting load: %s", selectedFile.c_str());
281 m_editor.requestSceneReload(selectedFile);
282 window->isOpen =
false;
292 ImGui::OpenPopup(
"Invalid Scene File");
296 if (ImGui::BeginPopupModal(
"Invalid Scene File",
nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
297 ImGui::Text(
"The selected file is not a valid VEX Scene file.");
298 ImGui::Text(
"Please select a .json file with scene data.");
302 if (ImGui::Button(
"OK", ImVec2(120, 0))) {
303 ImGui::CloseCurrentPopup();
312 m_Windows.push_back(openSceneWindow);
313 openSceneWindow->Create(m_ImGUIWrapper);
317 std::shared_ptr<BasicEditorWindow> openSceneWindow = std::make_shared<BasicEditorWindow>();
318 std::weak_ptr<BasicEditorWindow> weakWindow = openSceneWindow;
319 std::string startPath = m_editor.getProjectBinaryPath() +
"/Assets";
320 auto dialogBrowser = std::make_shared<vex::AssetBrowser>(startPath);
321 auto showError = std::make_shared<bool>(
false);
323 auto saveFolder = std::make_shared<std::string>(startPath);
324 auto fileName = std::make_shared<std::string>(
"NewScene.json");
326 auto* scene = m_editor.getSceneManager()->GetScene(m_editor.getSceneManager()->getLastSceneName());
328 openSceneWindow->Create = [
this, weakWindow, dialogBrowser, showError, scene, saveFolder, fileName](
vex::ImGUIWrapper& wrapper) {
329 wrapper.addUIFunction([=,
this]() {
330 auto window = weakWindow.lock();
if (!window || !window->isOpen)
return;
331 ImGui::SetNextWindowSize(ImVec2(600, 450), ImGuiCond_FirstUseEver);
333 if (ImGui::Begin(
"Save Scene As", &window->isOpen)) {
335 std::string selectedFile = dialogBrowser->Draw(m_editor.GetEditorIcons(), m_editor.getEditorProperties()->assetBrowserThumbnailSize);
337 if (!selectedFile.empty()) {
338 std::filesystem::path p(selectedFile);
340 if (p.has_extension()) {
341 *saveFolder = p.parent_path().string();
342 *fileName = p.filename().string();
344 *saveFolder = selectedFile;
347 else if (dialogBrowser->getCurrentPath() != *saveFolder) {
348 *saveFolder = dialogBrowser->getCurrentPath();
352 ImGui::Text(
"Save Options");
354 ImReflect::Input(
"Filename", *fileName);
356 if (ImGui::Button(
"Save", ImVec2(100, 0))) {
357 if (saveFolder->empty() || fileName->empty()) {
358 vex::log(
"Error: Folder or Filename cannot be empty.");
361 std::filesystem::path finalPath(*saveFolder);
362 finalPath /= *fileName;
364 if (!finalPath.has_extension()) {
365 finalPath +=
".json";
368 scene->Save(finalPath.string());
369 vex::log(
"Saved scene to: %s", finalPath.string().c_str());
370 window->isOpen =
false;
378 m_Windows.push_back(openSceneWindow);
379 openSceneWindow->Create(m_ImGUIWrapper);
402 std::string m_logs =
"";
403 bool m_isFinished =
false;
404 std::mutex m_logMutex;
405 bool m_autoScroll =
true;
407 auto state = std::make_shared<BuildState>();
409 std::string projectPath = m_editor.getProjectBinaryPath();
410 std::string configFlag = isDebug ?
" -d" :
" -r";
414 command =
"..\\..\\BuildTools\\build\\ProjectBuilder.exe \"" + projectPath +
"\"" + configFlag;
416 command =
"../../BuildTools/build/ProjectBuilder \"" + projectPath +
"\"" + configFlag;
419 vex::log(
"Starting Build: %s", command.c_str());
421 std::shared_ptr<BasicEditorWindow> buildWindow = std::make_shared<BasicEditorWindow>();
422 std::weak_ptr<BasicEditorWindow> weakWindow = buildWindow;
424 buildWindow->Create = [
this, weakWindow, state, isDebug](
vex::ImGUIWrapper& wrapper) {
425 wrapper.addUIFunction([=]() {
426 auto window = weakWindow.lock();
if (!window || !window->isOpen)
return;
428 ImGui::SetNextWindowSize(ImVec2(600, 400), ImGuiCond_FirstUseEver);
430 std::string title = isDebug ?
"Building Debug..." :
"Building Release...";
431 if (state->m_isFinished) title +=
" (Finished)";
433 if (ImGui::Begin(title.c_str(), &window->isOpen)) {
435 if(ImGui::Button(
"Clear")) {
436 std::lock_guard<std::mutex> lock(state->m_logMutex);
437 state->m_logs.clear();
440 ImGui::Checkbox(
"Auto-scroll", &state->m_autoScroll);
444 ImGui::BeginChild(
"BuildLogRegion", ImVec2(0, 0),
false, ImGuiWindowFlags_HorizontalScrollbar);
447 std::lock_guard<std::mutex> lock(state->m_logMutex);
448 ImGui::TextUnformatted(state->m_logs.c_str());
451 if (state->m_autoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) {
452 ImGui::SetScrollHereY(1.0f);
461 m_Windows.push_back(buildWindow);
462 buildWindow->Create(m_ImGUIWrapper);
464 std::thread([command, state,
this, isDebug, runAfter]() {
465 executeCommandRealTime(command,
466 [state](
const std::string& line) {
467 std::lock_guard<std::mutex> lock(state->m_logMutex);
468 state->m_logs += line;
474 if (projectName.empty()) {
475 std::lock_guard<std::mutex> lock(state->m_logMutex);
476 state->m_logs +=
"\n[Error] Could not retrieve Project Name from VexProject.json. Cannot run.\n";
478 std::filesystem::path runPath(m_editor.getProjectBinaryPath());
479 runPath = runPath /
"Build" / (isDebug ?
"Debug" :
"Release");
480 runPath = runPath / projectName;
486 if (std::filesystem::exists(runPath)) {
488 std::lock_guard<std::mutex> lock(state->m_logMutex);
489 state->m_logs +=
"\n=== Build Complete. Launching: " + runPath.string() +
" ===\n";
492 std::string runCmd =
"\"" + runPath.string() +
"\"";
494 executeCommandRealTime(runCmd,
495 [state](
const std::string& line) {
496 std::lock_guard<std::mutex> lock(state->m_logMutex);
497 state->m_logs += line;
501 std::lock_guard<std::mutex> lock(state->m_logMutex);
502 state->m_logs +=
"\n[Error] Executable not found at: " + runPath.string() +
"\n";
508 std::lock_guard<std::mutex> lock(state->m_logMutex);
509 state->m_logs +=
"\n=== Build Finished ===\n";
510 state->m_isFinished =
true;
517 std::string m_logs =
"";
518 bool m_isFinished =
false;
519 std::mutex m_logMutex;
520 bool m_autoScroll =
true;
522 auto state = std::make_shared<BuildState>();
524 std::string projectPath;
525 std::string enginePath;
528 projectPath = std::filesystem::canonical(m_editor.getProjectBinaryPath()).string();
530 }
catch (
const std::exception& e) {
531 vex::log(
"Error resolving paths for Dist Build: %s", e.what());
538 command =
"..\\..\\BuildTools\\build\\ProjectBuilder.exe \"" + projectPath +
"\" -dist";
539 vex::log(
"Starting Windows Distribution Build: %s", command.c_str());
542 std::string image =
"vex-builder:latest";
543 std::string containerEnginePath =
"/VexEngine";
544 std::string containerProjectPath =
"/VexProject";
546 std::string shellCmd =
547 "echo '>> Compiling ProjectBuilder (Container)...' && "
548 "cd " + containerEnginePath +
"/BuildTools && "
549 "chmod +x rebuild-buildtools-linux.sh && "
550 "./rebuild-buildtools-linux.sh build_dist && "
551 "echo '>> Starting Project Distribution Build...' && "
552 "./build_dist/ProjectBuilder " + containerProjectPath +
" -dist";
554 std::stringstream cmdBuilder;
556 bool isPodman = (system(
"which podman > /dev/null 2>&1") == 0);
557 if (isPodman) cmdBuilder <<
"podman";
558 else cmdBuilder <<
"docker";
560 std::string mountOpts =
":rw,z";
562 cmdBuilder <<
" run --rm ";
565 cmdBuilder <<
"--userns=keep-id ";
567 cmdBuilder <<
"--user $(id -u):$(id -g) ";
570 cmdBuilder <<
"-v \"" << enginePath <<
":" << containerEnginePath << mountOpts <<
"\" "
571 <<
"-v \"" << projectPath <<
":" << containerProjectPath << mountOpts <<
"\" "
572 <<
"-w " << containerEnginePath <<
" "
574 <<
"/bin/bash -c \"" << shellCmd <<
"\"";
576 command = cmdBuilder.str() +
" 2>&1";
578 vex::log(
"Starting Linux (Sniper) Distribution Build...");
581 std::shared_ptr<BasicEditorWindow> buildWindow = std::make_shared<BasicEditorWindow>();
582 std::weak_ptr<BasicEditorWindow> weakWindow = buildWindow;
585 wrapper.addUIFunction([=]() {
586 auto window = weakWindow.lock();
if (!window || !window->isOpen)
return;
587 ImGui::SetNextWindowSize(ImVec2(800, 500), ImGuiCond_FirstUseEver);
589 if (ImGui::Begin(
"Distribution Build (Shipping)", &window->isOpen)) {
590 if(ImGui::Button(
"Clear")) {
591 std::lock_guard<std::mutex> lock(state->m_logMutex);
592 state->m_logs.clear();
595 ImGui::Checkbox(
"Auto-scroll", &state->m_autoScroll);
599 ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.0f, 1.0f),
"[Linux] Building in Special Container.");
602 if (state->m_isFinished) {
603 ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f),
"Status: Finished");
605 ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.0f, 1.0f),
"Status: Working...");
608 ImGui::BeginChild(
"BuildLogRegion", ImVec2(0, 0),
false, ImGuiWindowFlags_HorizontalScrollbar);
610 std::lock_guard<std::mutex> lock(state->m_logMutex);
611 ImGui::TextUnformatted(state->m_logs.c_str());
613 if (state->m_autoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) {
614 ImGui::SetScrollHereY(1.0f);
622 m_Windows.push_back(buildWindow);
623 buildWindow->Create(m_ImGUIWrapper);
625 std::thread([command, state]() {
627 executeCommandRealTime(command,
628 [state](
const std::string& line) {
630 std::lock_guard<std::mutex> lock(state->m_logMutex);
631 state->m_logs += line;
634 if (line.length() > 1) {
639 }
catch (
const std::exception& e) {
640 std::lock_guard<std::mutex> lock(state->m_logMutex);
641 state->m_logs +=
"\n[CRITICAL ERROR] Execution failed: ";
642 state->m_logs += e.what();
646 std::lock_guard<std::mutex> lock(state->m_logMutex);
647 state->m_logs +=
"\n=== Distribution Build Finished ===\n";
648 state->m_isFinished =
true;
654 std::shared_ptr<BasicEditorWindow> newSceneWindow = std::make_shared<BasicEditorWindow>();
655 std::weak_ptr<BasicEditorWindow> weakWindow = newSceneWindow;
656 std::string startPath = m_editor.getProjectBinaryPath() +
"/Assets";
657 auto dialogBrowser = std::make_shared<vex::AssetBrowser>(startPath);
659 auto saveFolder = std::make_shared<std::string>(startPath);
660 auto fileName = std::make_shared<std::string>(
"NewScene.json");
662 newSceneWindow->Create = [
this, weakWindow, dialogBrowser, saveFolder, fileName](
vex::ImGUIWrapper& wrapper) {
663 wrapper.addUIFunction([=,
this]() {
664 auto window = weakWindow.lock();
if (!window || !window->isOpen)
return;
665 ImGui::SetNextWindowSize(ImVec2(600, 450), ImGuiCond_FirstUseEver);
667 if (ImGui::Begin(
"Create New Scene", &window->isOpen)) {
669 std::string selectedFile = dialogBrowser->Draw(m_editor.GetEditorIcons(), m_editor.getEditorProperties()->assetBrowserThumbnailSize);
671 if (!selectedFile.empty()) {
672 std::filesystem::path p(selectedFile);
673 if (p.has_extension()) {
674 *saveFolder = p.parent_path().string();
675 *fileName = p.filename().string();
677 *saveFolder = selectedFile;
680 else if (dialogBrowser->getCurrentPath() != *saveFolder) {
681 *saveFolder = dialogBrowser->getCurrentPath();
685 ImGui::Text(
"New Scene Options");
687 ImReflect::Input(
"Filename", *fileName);
689 if (ImGui::Button(
"Create & Open", ImVec2(120, 0))) {
690 if (saveFolder->empty() || fileName->empty()) {
691 vex::log(
"Error: Folder or Filename cannot be empty.");
694 std::filesystem::path destPath(*saveFolder);
695 destPath /= *fileName;
697 if (!destPath.has_extension()) {
701 std::filesystem::path sourcePath = std::filesystem::path(
"../Assets/default/default.json");
704 if (std::filesystem::exists(sourcePath)) {
705 std::filesystem::copy_file(sourcePath, destPath, std::filesystem::copy_options::overwrite_existing);
706 vex::log(
"New scene created: %s", destPath.string().c_str());
708 m_editor.requestSceneReload(destPath.string());
709 window->isOpen =
false;
712 vex::log(
"Error: Could not find default scene template at: %s", sourcePath.string().c_str());
715 catch (
const std::filesystem::filesystem_error& e) {
716 vex::log(
"Filesystem Error: %s", e.what());
725 m_Windows.push_back(newSceneWindow);
726 newSceneWindow->Create(m_ImGUIWrapper);