added Git Package Manager
This commit is contained in:
@@ -20,13 +20,13 @@ Collapsed=0
|
|||||||
|
|
||||||
[Window][Viewport]
|
[Window][Viewport]
|
||||||
Pos=306,46
|
Pos=306,46
|
||||||
Size=1265,737
|
Size=1265,739
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000002,0
|
DockId=0x00000002,0
|
||||||
|
|
||||||
[Window][Hierarchy]
|
[Window][Hierarchy]
|
||||||
Pos=0,46
|
Pos=0,46
|
||||||
Size=304,737
|
Size=304,739
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000001,0
|
DockId=0x00000001,0
|
||||||
|
|
||||||
@@ -43,14 +43,14 @@ Collapsed=0
|
|||||||
DockId=0x00000006,1
|
DockId=0x00000006,1
|
||||||
|
|
||||||
[Window][Console]
|
[Window][Console]
|
||||||
Pos=0,785
|
Pos=0,787
|
||||||
Size=785,221
|
Size=785,219
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000005,0
|
DockId=0x00000005,0
|
||||||
|
|
||||||
[Window][Project]
|
[Window][Project]
|
||||||
Pos=787,785
|
Pos=787,787
|
||||||
Size=784,221
|
Size=784,219
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000006,0
|
DockId=0x00000006,0
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ Collapsed=0
|
|||||||
|
|
||||||
[Window][Camera]
|
[Window][Camera]
|
||||||
Pos=0,46
|
Pos=0,46
|
||||||
Size=304,737
|
Size=304,739
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000001,1
|
DockId=0x00000001,1
|
||||||
|
|
||||||
@@ -79,18 +79,24 @@ DockId=0x00000006,1
|
|||||||
|
|
||||||
[Window][Game Viewport]
|
[Window][Game Viewport]
|
||||||
Pos=306,46
|
Pos=306,46
|
||||||
Size=1265,737
|
Size=1265,739
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000002,1
|
DockId=0x00000002,1
|
||||||
|
|
||||||
|
[Window][Project Settings]
|
||||||
|
Pos=306,46
|
||||||
|
Size=1265,739
|
||||||
|
Collapsed=0
|
||||||
|
DockId=0x00000002,2
|
||||||
|
|
||||||
[Docking][Data]
|
[Docking][Data]
|
||||||
DockSpace ID=0xD71539A0 Window=0x3DA2F1DE Pos=0,46 Size=1920,960 Split=X
|
DockSpace ID=0xD71539A0 Window=0x3DA2F1DE Pos=0,46 Size=1920,960 Split=X
|
||||||
DockNode ID=0x00000007 Parent=0xD71539A0 SizeRef=1509,1015 Split=Y
|
DockNode ID=0x00000007 Parent=0xD71539A0 SizeRef=1509,1015 Split=Y
|
||||||
DockNode ID=0x00000003 Parent=0x00000007 SizeRef=1858,792 Split=X
|
DockNode ID=0x00000003 Parent=0x00000007 SizeRef=1858,739 Split=X
|
||||||
DockNode ID=0x00000001 Parent=0x00000003 SizeRef=304,758 Selected=0xBABDAE5E
|
DockNode ID=0x00000001 Parent=0x00000003 SizeRef=304,758 Selected=0xBABDAE5E
|
||||||
DockNode ID=0x00000002 Parent=0x00000003 SizeRef=694,758 CentralNode=1 Selected=0xC450F867
|
DockNode ID=0x00000002 Parent=0x00000003 SizeRef=694,758 CentralNode=1 Selected=0xC450F867
|
||||||
DockNode ID=0x00000004 Parent=0x00000007 SizeRef=1858,221 Split=X Selected=0xEA83D666
|
DockNode ID=0x00000004 Parent=0x00000007 SizeRef=1858,219 Split=X Selected=0xEA83D666
|
||||||
DockNode ID=0x00000005 Parent=0x00000004 SizeRef=929,221 Selected=0xEA83D666
|
DockNode ID=0x00000005 Parent=0x00000004 SizeRef=929,221 Selected=0xEA83D666
|
||||||
DockNode ID=0x00000006 Parent=0x00000004 SizeRef=927,221 Selected=0xDA0DCE3C
|
DockNode ID=0x00000006 Parent=0x00000004 SizeRef=927,221 Selected=0x9C21DE82
|
||||||
DockNode ID=0x00000008 Parent=0xD71539A0 SizeRef=347,1015 Selected=0x36DC96AB
|
DockNode ID=0x00000008 Parent=0xD71539A0 SizeRef=347,1015 Selected=0x36DC96AB
|
||||||
|
|
||||||
|
|||||||
@@ -271,7 +271,7 @@ void applyModernTheme() {
|
|||||||
style.TabBorderSize = 1.0f;
|
style.TabBorderSize = 1.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setupDockspace() {
|
void setupDockspace(const std::function<void()>& menuBarContent) {
|
||||||
static bool dockspaceOpen = true;
|
static bool dockspaceOpen = true;
|
||||||
static ImGuiDockNodeFlags dockspaceFlags = ImGuiDockNodeFlags_None;
|
static ImGuiDockNodeFlags dockspaceFlags = ImGuiDockNodeFlags_None;
|
||||||
|
|
||||||
@@ -291,6 +291,13 @@ void setupDockspace() {
|
|||||||
ImGui::Begin("DockSpace", &dockspaceOpen, windowFlags);
|
ImGui::Begin("DockSpace", &dockspaceOpen, windowFlags);
|
||||||
ImGui::PopStyleVar(3);
|
ImGui::PopStyleVar(3);
|
||||||
|
|
||||||
|
if (ImGui::BeginMenuBar()) {
|
||||||
|
if (menuBarContent) {
|
||||||
|
menuBarContent();
|
||||||
|
}
|
||||||
|
ImGui::EndMenuBar();
|
||||||
|
}
|
||||||
|
|
||||||
ImGuiID dockspaceId = ImGui::GetID("MainDockspace");
|
ImGuiID dockspaceId = ImGui::GetID("MainDockspace");
|
||||||
ImGui::DockSpace(dockspaceId, ImVec2(0.0f, 0.0f), dockspaceFlags);
|
ImGui::DockSpace(dockspaceId, ImVec2(0.0f, 0.0f), dockspaceFlags);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
#include "Common.h"
|
#include "Common.h"
|
||||||
|
|
||||||
enum class FileBrowserViewMode {
|
enum class FileBrowserViewMode {
|
||||||
@@ -61,4 +62,4 @@ public:
|
|||||||
void applyModernTheme();
|
void applyModernTheme();
|
||||||
|
|
||||||
// Setup ImGui dockspace for the editor
|
// Setup ImGui dockspace for the editor
|
||||||
void setupDockspace();
|
void setupDockspace(const std::function<void()>& menuBarContent = nullptr);
|
||||||
|
|||||||
@@ -380,7 +380,7 @@ void Engine::run() {
|
|||||||
}
|
}
|
||||||
renderLauncher();
|
renderLauncher();
|
||||||
} else {
|
} else {
|
||||||
setupDockspace();
|
setupDockspace([this]() { renderPlayControlsBar(); });
|
||||||
renderMainMenuBar();
|
renderMainMenuBar();
|
||||||
|
|
||||||
if (!viewportFullscreen) {
|
if (!viewportFullscreen) {
|
||||||
@@ -910,6 +910,8 @@ void Engine::OpenProjectPath(const std::string& path) {
|
|||||||
fs::create_directories(projectManager.currentProject.scenesPath);
|
fs::create_directories(projectManager.currentProject.scenesPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
packageManager.setProjectRoot(projectManager.currentProject.projectPath);
|
||||||
|
|
||||||
if (!initRenderer()) {
|
if (!initRenderer()) {
|
||||||
addConsoleMessage("Error: Failed to initialize renderer!", ConsoleMessageType::Error);
|
addConsoleMessage("Error: Failed to initialize renderer!", ConsoleMessageType::Error);
|
||||||
showLauncher = true;
|
showLauncher = true;
|
||||||
@@ -948,6 +950,8 @@ void Engine::createNewProject(const char* name, const char* location) {
|
|||||||
projectManager.addToRecentProjects(name,
|
projectManager.addToRecentProjects(name,
|
||||||
(newProject.projectPath / "project.modu").string());
|
(newProject.projectPath / "project.modu").string());
|
||||||
|
|
||||||
|
packageManager.setProjectRoot(projectManager.currentProject.projectPath);
|
||||||
|
|
||||||
if (!initRenderer()) {
|
if (!initRenderer()) {
|
||||||
logToConsole("Error: Failed to initialize renderer!");
|
logToConsole("Error: Failed to initialize renderer!");
|
||||||
return;
|
return;
|
||||||
@@ -1329,6 +1333,8 @@ void Engine::compileScriptFile(const fs::path& scriptPath) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
packageManager.applyToBuildConfig(config);
|
||||||
|
|
||||||
ScriptBuildCommands commands;
|
ScriptBuildCommands commands;
|
||||||
if (!scriptCompiler.makeCommands(config, scriptPath, commands, error)) {
|
if (!scriptCompiler.makeCommands(config, scriptPath, commands, error)) {
|
||||||
lastCompileSuccess = false;
|
lastCompileSuccess = false;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "ScriptRuntime.h"
|
#include "ScriptRuntime.h"
|
||||||
#include "PhysicsSystem.h"
|
#include "PhysicsSystem.h"
|
||||||
#include "AudioSystem.h"
|
#include "AudioSystem.h"
|
||||||
|
#include "PackageManager.h"
|
||||||
#include "../include/Window/Window.h"
|
#include "../include/Window/Window.h"
|
||||||
|
|
||||||
void window_size_callback(GLFWwindow* window, int width, int height);
|
void window_size_callback(GLFWwindow* window, int width, int height);
|
||||||
@@ -71,6 +72,7 @@ private:
|
|||||||
int draggedObjectId = -1;
|
int draggedObjectId = -1;
|
||||||
|
|
||||||
ProjectManager projectManager;
|
ProjectManager projectManager;
|
||||||
|
PackageManager packageManager;
|
||||||
bool showLauncher = true;
|
bool showLauncher = true;
|
||||||
bool showNewSceneDialog = false;
|
bool showNewSceneDialog = false;
|
||||||
bool showSaveSceneAsDialog = false;
|
bool showSaveSceneAsDialog = false;
|
||||||
@@ -97,6 +99,8 @@ private:
|
|||||||
int previewCameraId = -1;
|
int previewCameraId = -1;
|
||||||
bool gameViewCursorLocked = false;
|
bool gameViewCursorLocked = false;
|
||||||
bool gameViewportFocused = false;
|
bool gameViewportFocused = false;
|
||||||
|
bool showUITextOverlay = false;
|
||||||
|
bool showCanvasOverlay = false;
|
||||||
int activePlayerId = -1;
|
int activePlayerId = -1;
|
||||||
MeshBuilder meshBuilder;
|
MeshBuilder meshBuilder;
|
||||||
char meshBuilderPath[260] = "";
|
char meshBuilderPath[260] = "";
|
||||||
@@ -142,6 +146,7 @@ private:
|
|||||||
void renderNewProjectDialog();
|
void renderNewProjectDialog();
|
||||||
void renderOpenProjectDialog();
|
void renderOpenProjectDialog();
|
||||||
void renderMainMenuBar();
|
void renderMainMenuBar();
|
||||||
|
void renderPlayControlsBar();
|
||||||
void renderEnvironmentWindow();
|
void renderEnvironmentWindow();
|
||||||
void renderCameraWindow();
|
void renderCameraWindow();
|
||||||
void renderHierarchyPanel();
|
void renderHierarchyPanel();
|
||||||
|
|||||||
@@ -10,6 +10,9 @@
|
|||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <future>
|
||||||
|
#include <chrono>
|
||||||
|
#include <future>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <shlobj.h>
|
#include <shlobj.h>
|
||||||
@@ -298,6 +301,106 @@ namespace FileIcons {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace ImGui {
|
||||||
|
|
||||||
|
// Animated progress bar that keeps circles moving while work happens in the background.
|
||||||
|
bool BufferingBar(const char* label, float value, const ImVec2& size_arg, const ImU32& bg_col, const ImU32& fg_col) {
|
||||||
|
ImGuiWindow* window = GetCurrentWindow();
|
||||||
|
if (window->SkipItems)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ImGuiContext& g = *GImGui;
|
||||||
|
const ImGuiStyle& style = g.Style;
|
||||||
|
const ImGuiID id = window->GetID(label);
|
||||||
|
|
||||||
|
ImVec2 pos = window->DC.CursorPos;
|
||||||
|
ImVec2 size = size_arg;
|
||||||
|
size.x -= style.FramePadding.x * 2;
|
||||||
|
|
||||||
|
const ImRect bb(pos, ImVec2(pos.x + size.x, pos.y + size.y));
|
||||||
|
ItemSize(bb, style.FramePadding.y);
|
||||||
|
if (!ItemAdd(bb, id))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const float circleStart = size.x * 0.7f;
|
||||||
|
const float circleEnd = size.x;
|
||||||
|
const float circleWidth = circleEnd - circleStart;
|
||||||
|
|
||||||
|
window->DrawList->AddRectFilled(bb.Min, ImVec2(pos.x + circleStart, bb.Max.y), bg_col);
|
||||||
|
window->DrawList->AddRectFilled(bb.Min, ImVec2(pos.x + circleStart * value, bb.Max.y), fg_col);
|
||||||
|
|
||||||
|
const float t = g.Time;
|
||||||
|
const float r = size.y / 2;
|
||||||
|
const float speed = 1.5f;
|
||||||
|
|
||||||
|
const float a = speed * 0;
|
||||||
|
const float b = speed * 0.333f;
|
||||||
|
const float c = speed * 0.666f;
|
||||||
|
|
||||||
|
const float o1 = (circleWidth + r) * (t + a - speed * (int)((t + a) / speed)) / speed;
|
||||||
|
const float o2 = (circleWidth + r) * (t + b - speed * (int)((t + b) / speed)) / speed;
|
||||||
|
const float o3 = (circleWidth + r) * (t + c - speed * (int)((t + c) / speed)) / speed;
|
||||||
|
|
||||||
|
window->DrawList->AddCircleFilled(ImVec2(pos.x + circleEnd - o1, bb.Min.y + r), r, bg_col);
|
||||||
|
window->DrawList->AddCircleFilled(ImVec2(pos.x + circleEnd - o2, bb.Min.y + r), r, bg_col);
|
||||||
|
window->DrawList->AddCircleFilled(ImVec2(pos.x + circleEnd - o3, bb.Min.y + r), r, bg_col);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple loading spinner for indeterminate tasks.
|
||||||
|
bool Spinner(const char* label, float radius, int thickness, const ImU32& color) {
|
||||||
|
ImGuiWindow* window = GetCurrentWindow();
|
||||||
|
if (window->SkipItems)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ImGuiContext& g = *GImGui;
|
||||||
|
const ImGuiStyle& style = g.Style;
|
||||||
|
const ImGuiID id = window->GetID(label);
|
||||||
|
|
||||||
|
ImVec2 pos = window->DC.CursorPos;
|
||||||
|
ImVec2 size((radius) * 2, (radius + style.FramePadding.y) * 2);
|
||||||
|
|
||||||
|
const ImRect bb(pos, ImVec2(pos.x + size.x, pos.y + size.y));
|
||||||
|
ItemSize(bb, style.FramePadding.y);
|
||||||
|
if (!ItemAdd(bb, id))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
window->DrawList->PathClear();
|
||||||
|
|
||||||
|
int num_segments = 30;
|
||||||
|
int start = abs(ImSin(g.Time * 1.8f) * (num_segments - 5));
|
||||||
|
|
||||||
|
const float a_min = IM_PI * 2.0f * ((float)start) / (float)num_segments;
|
||||||
|
const float a_max = IM_PI * 2.0f * ((float)num_segments - 3) / (float)num_segments;
|
||||||
|
|
||||||
|
const ImVec2 centre = ImVec2(pos.x + radius, pos.y + radius + style.FramePadding.y);
|
||||||
|
|
||||||
|
for (int i = 0; i < num_segments; i++) {
|
||||||
|
const float a = a_min + ((float)i / (float)num_segments) * (a_max - a_min);
|
||||||
|
window->DrawList->PathLineTo(ImVec2(centre.x + ImCos(a + g.Time * 8) * radius,
|
||||||
|
centre.y + ImSin(a + g.Time * 8) * radius));
|
||||||
|
}
|
||||||
|
|
||||||
|
window->DrawList->PathStroke(color, false, thickness);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ImGui
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
struct PackageTaskResult {
|
||||||
|
bool success = false;
|
||||||
|
std::string message;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PackageTaskState {
|
||||||
|
bool active = false;
|
||||||
|
float startTime = 0.0f;
|
||||||
|
std::string label;
|
||||||
|
std::future<PackageTaskResult> future;
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
void Engine::renderGameViewportWindow() {
|
void Engine::renderGameViewportWindow() {
|
||||||
gameViewportFocused = false;
|
gameViewportFocused = false;
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(6.0f, 6.0f));
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(6.0f, 6.0f));
|
||||||
@@ -308,30 +411,75 @@ void Engine::renderGameViewportWindow() {
|
|||||||
int width = std::max(160, (int)avail.x);
|
int width = std::max(160, (int)avail.x);
|
||||||
int height = std::max(120, (int)avail.y);
|
int height = std::max(120, (int)avail.y);
|
||||||
|
|
||||||
const SceneObject* playerCam = nullptr;
|
SceneObject* playerCam = nullptr;
|
||||||
for (const auto& obj : sceneObjects) {
|
for (auto& obj : sceneObjects) {
|
||||||
if (obj.type == ObjectType::Camera && obj.camera.type == SceneCameraType::Player) {
|
if (obj.type == ObjectType::Camera && obj.camera.type == SceneCameraType::Player) {
|
||||||
playerCam = &obj;
|
playerCam = &obj;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.08f, 0.09f, 0.10f, 1.0f));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.12f, 0.14f, 0.16f, 1.0f));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.14f, 0.18f, 0.20f, 1.0f));
|
||||||
|
ImGui::BeginDisabled(playerCam == nullptr);
|
||||||
|
bool dummyToggle = false;
|
||||||
|
bool postFxChanged = false;
|
||||||
|
if (playerCam) {
|
||||||
|
bool before = playerCam->camera.applyPostFX;
|
||||||
|
if (ImGui::Checkbox("Post FX", &playerCam->camera.applyPostFX)) {
|
||||||
|
postFxChanged = (before != playerCam->camera.applyPostFX);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ImGui::Checkbox("Post FX", &dummyToggle);
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::Checkbox("Text", &showUITextOverlay);
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::Checkbox("Canvas Guides", &showCanvasOverlay);
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
ImGui::PopStyleColor(3);
|
||||||
|
|
||||||
|
if (playerCam && postFxChanged) {
|
||||||
|
projectManager.currentProject.hasUnsavedChanges = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!isPlaying) {
|
if (!isPlaying) {
|
||||||
gameViewCursorLocked = false;
|
gameViewCursorLocked = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (playerCam && rendererInitialized) {
|
if (playerCam && rendererInitialized) {
|
||||||
unsigned int tex = renderer.renderScenePreview(
|
unsigned int tex = renderer.renderScenePreview(
|
||||||
makeCameraFromObject(*playerCam),
|
makeCameraFromObject(*playerCam),
|
||||||
sceneObjects,
|
sceneObjects,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
playerCam->camera.fov,
|
playerCam->camera.fov,
|
||||||
playerCam->camera.nearClip,
|
playerCam->camera.nearClip,
|
||||||
playerCam->camera.farClip
|
playerCam->camera.farClip,
|
||||||
|
playerCam->camera.applyPostFX
|
||||||
);
|
);
|
||||||
|
|
||||||
ImGui::Image((void*)(intptr_t)tex, ImVec2((float)width, (float)height), ImVec2(0, 1), ImVec2(1, 0));
|
ImGui::Image((void*)(intptr_t)tex, ImVec2((float)width, (float)height), ImVec2(0, 1), ImVec2(1, 0));
|
||||||
|
ImVec2 imageMin = ImGui::GetItemRectMin();
|
||||||
|
ImVec2 imageMax = ImGui::GetItemRectMax();
|
||||||
|
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||||
|
if (showCanvasOverlay) {
|
||||||
|
ImVec2 pad(8.0f, 8.0f);
|
||||||
|
ImVec2 tl(imageMin.x + pad.x, imageMin.y + pad.y);
|
||||||
|
ImVec2 br(imageMax.x - pad.x, imageMax.y - pad.y);
|
||||||
|
drawList->AddRect(tl, br, IM_COL32(110, 170, 255, 180), 8.0f, 0, 2.0f);
|
||||||
|
}
|
||||||
|
if (showUITextOverlay) {
|
||||||
|
const char* textLabel = "Text Overlay";
|
||||||
|
ImVec2 textPos(imageMin.x + 16.0f, imageMin.y + 16.0f);
|
||||||
|
ImVec2 size = ImGui::CalcTextSize(textLabel);
|
||||||
|
ImVec2 bgPad(6.0f, 4.0f);
|
||||||
|
ImVec2 bgMin(textPos.x - bgPad.x, textPos.y - bgPad.y);
|
||||||
|
ImVec2 bgMax(textPos.x + size.x + bgPad.x, textPos.y + size.y + bgPad.y);
|
||||||
|
drawList->AddRectFilled(bgMin, bgMax, IM_COL32(20, 20, 24, 200), 4.0f);
|
||||||
|
drawList->AddText(textPos, IM_COL32(235, 235, 245, 255), textLabel);
|
||||||
|
}
|
||||||
bool hovered = ImGui::IsItemHovered();
|
bool hovered = ImGui::IsItemHovered();
|
||||||
bool clicked = hovered && isPlaying && ImGui::IsMouseClicked(ImGuiMouseButton_Left);
|
bool clicked = hovered && isPlaying && ImGui::IsMouseClicked(ImGuiMouseButton_Left);
|
||||||
|
|
||||||
@@ -1302,6 +1450,84 @@ void Engine::renderOpenProjectDialog() {
|
|||||||
ImGui::End();
|
ImGui::End();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Engine::renderPlayControlsBar() {
|
||||||
|
ImGuiStyle& style = ImGui::GetStyle();
|
||||||
|
ImVec2 buttonPadding(10.0f, 4.0f);
|
||||||
|
const char* playLabel = isPlaying ? "Stop" : "Play";
|
||||||
|
const char* pauseLabel = isPaused ? "Resume" : "Pause";
|
||||||
|
const char* specLabel = specMode ? "Spec On" : "Spec Mode";
|
||||||
|
|
||||||
|
auto buttonWidth = [&](const char* label) {
|
||||||
|
ImVec2 textSize = ImGui::CalcTextSize(label);
|
||||||
|
return textSize.x + buttonPadding.x * 2.0f + style.FrameBorderSize * 2.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
float playWidth = buttonWidth(playLabel);
|
||||||
|
float pauseWidth = buttonWidth(pauseLabel);
|
||||||
|
float specWidth = buttonWidth(specLabel);
|
||||||
|
float spacing = style.ItemSpacing.x;
|
||||||
|
float totalWidth = playWidth + pauseWidth + specWidth + spacing * 2.0f;
|
||||||
|
|
||||||
|
// Center the controls inside the dockspace menu bar.
|
||||||
|
float regionMinX = ImGui::GetWindowContentRegionMin().x;
|
||||||
|
float regionMaxX = ImGui::GetWindowContentRegionMax().x;
|
||||||
|
float regionWidth = regionMaxX - regionMinX;
|
||||||
|
float startX = (regionWidth - totalWidth) * 0.5f + regionMinX;
|
||||||
|
if (startX < regionMinX) startX = regionMinX;
|
||||||
|
|
||||||
|
ImVec2 cursor = ImGui::GetCursorPos();
|
||||||
|
ImGui::SetCursorPos(ImVec2(startX, cursor.y));
|
||||||
|
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, buttonPadding);
|
||||||
|
bool playPressed = ImGui::Button(playLabel);
|
||||||
|
ImGui::SameLine(0.0f, spacing);
|
||||||
|
bool pausePressed = ImGui::Button(pauseLabel);
|
||||||
|
ImGui::SameLine(0.0f, spacing);
|
||||||
|
bool specPressed = ImGui::Button(specLabel);
|
||||||
|
ImGui::PopStyleVar();
|
||||||
|
|
||||||
|
if (playPressed) {
|
||||||
|
bool newState = !isPlaying;
|
||||||
|
if (newState) {
|
||||||
|
if (physics.isReady() || physics.init()) {
|
||||||
|
physics.onPlayStart(sceneObjects);
|
||||||
|
} else {
|
||||||
|
addConsoleMessage("PhysX failed to initialize; physics disabled for play mode", ConsoleMessageType::Warning);
|
||||||
|
}
|
||||||
|
audio.onPlayStart(sceneObjects);
|
||||||
|
} else {
|
||||||
|
physics.onPlayStop();
|
||||||
|
audio.onPlayStop();
|
||||||
|
isPaused = false;
|
||||||
|
if (specMode && (physics.isReady() || physics.init())) {
|
||||||
|
physics.onPlayStart(sceneObjects);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isPlaying = newState;
|
||||||
|
}
|
||||||
|
if (pausePressed) {
|
||||||
|
isPaused = !isPaused;
|
||||||
|
if (isPaused) isPlaying = true; // placeholder: pausing implies we’re in play mode
|
||||||
|
}
|
||||||
|
if (specPressed) {
|
||||||
|
bool enable = !specMode;
|
||||||
|
if (enable && !physics.isReady() && !physics.init()) {
|
||||||
|
addConsoleMessage("PhysX failed to initialize; spec mode disabled", ConsoleMessageType::Warning);
|
||||||
|
enable = false;
|
||||||
|
}
|
||||||
|
specMode = enable;
|
||||||
|
if (!isPlaying) {
|
||||||
|
if (specMode) {
|
||||||
|
physics.onPlayStart(sceneObjects);
|
||||||
|
audio.onPlayStart(sceneObjects);
|
||||||
|
} else {
|
||||||
|
physics.onPlayStop();
|
||||||
|
audio.onPlayStop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Engine::renderMainMenuBar() {
|
void Engine::renderMainMenuBar() {
|
||||||
if (ImGui::BeginMainMenuBar()) {
|
if (ImGui::BeginMainMenuBar()) {
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(14.0f, 8.0f));
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(14.0f, 8.0f));
|
||||||
@@ -1417,59 +1643,6 @@ void Engine::renderMainMenuBar() {
|
|||||||
"No Scene Loaded" : projectManager.currentProject.currentSceneName;
|
"No Scene Loaded" : projectManager.currentProject.currentSceneName;
|
||||||
ImGui::TextUnformatted(sceneLabel.c_str());
|
ImGui::TextUnformatted(sceneLabel.c_str());
|
||||||
|
|
||||||
ImGui::SameLine();
|
|
||||||
ImGui::Dummy(ImVec2(14.0f, 0.0f));
|
|
||||||
ImGui::SameLine();
|
|
||||||
|
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 4.0f));
|
|
||||||
bool playPressed = ImGui::Button(isPlaying ? "Stop" : "Play");
|
|
||||||
ImGui::SameLine(0.0f, 6.0f);
|
|
||||||
bool pausePressed = ImGui::Button(isPaused ? "Resume" : "Pause");
|
|
||||||
ImGui::SameLine(0.0f, 6.0f);
|
|
||||||
bool specPressed = ImGui::Button(specMode ? "Spec On" : "Spec Mode");
|
|
||||||
ImGui::PopStyleVar();
|
|
||||||
|
|
||||||
if (playPressed) {
|
|
||||||
bool newState = !isPlaying;
|
|
||||||
if (newState) {
|
|
||||||
if (physics.isReady() || physics.init()) {
|
|
||||||
physics.onPlayStart(sceneObjects);
|
|
||||||
} else {
|
|
||||||
addConsoleMessage("PhysX failed to initialize; physics disabled for play mode", ConsoleMessageType::Warning);
|
|
||||||
}
|
|
||||||
audio.onPlayStart(sceneObjects);
|
|
||||||
} else {
|
|
||||||
physics.onPlayStop();
|
|
||||||
audio.onPlayStop();
|
|
||||||
isPaused = false;
|
|
||||||
if (specMode && (physics.isReady() || physics.init())) {
|
|
||||||
physics.onPlayStart(sceneObjects);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
isPlaying = newState;
|
|
||||||
}
|
|
||||||
if (pausePressed) {
|
|
||||||
isPaused = !isPaused;
|
|
||||||
if (isPaused) isPlaying = true; // placeholder: pausing implies we’re in play mode
|
|
||||||
}
|
|
||||||
if (specPressed) {
|
|
||||||
bool enable = !specMode;
|
|
||||||
if (enable && !physics.isReady() && !physics.init()) {
|
|
||||||
addConsoleMessage("PhysX failed to initialize; spec mode disabled", ConsoleMessageType::Warning);
|
|
||||||
enable = false;
|
|
||||||
}
|
|
||||||
specMode = enable;
|
|
||||||
if (!isPlaying) {
|
|
||||||
if (specMode) {
|
|
||||||
physics.onPlayStart(sceneObjects);
|
|
||||||
audio.onPlayStart(sceneObjects);
|
|
||||||
} else {
|
|
||||||
physics.onPlayStop();
|
|
||||||
audio.onPlayStop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
float rightX = ImGui::GetWindowWidth() - 220.0f;
|
float rightX = ImGui::GetWindowWidth() - 220.0f;
|
||||||
if (rightX > ImGui::GetCursorPosX()) {
|
if (rightX > ImGui::GetCursorPosX()) {
|
||||||
ImGui::SameLine(rightX);
|
ImGui::SameLine(rightX);
|
||||||
@@ -2497,6 +2670,9 @@ void Engine::renderInspectorPanel() {
|
|||||||
obj.camera.farClip = std::max(obj.camera.nearClip + 0.05f, obj.camera.farClip);
|
obj.camera.farClip = std::max(obj.camera.nearClip + 0.05f, obj.camera.farClip);
|
||||||
projectManager.currentProject.hasUnsavedChanges = true;
|
projectManager.currentProject.hasUnsavedChanges = true;
|
||||||
}
|
}
|
||||||
|
if (ImGui::Checkbox("Apply Post Processing", &obj.camera.applyPostFX)) {
|
||||||
|
projectManager.currentProject.hasUnsavedChanges = true;
|
||||||
|
}
|
||||||
ImGui::Unindent(10.0f);
|
ImGui::Unindent(10.0f);
|
||||||
ImGui::PopID();
|
ImGui::PopID();
|
||||||
}
|
}
|
||||||
@@ -4661,7 +4837,8 @@ void Engine::renderViewport() {
|
|||||||
previewHeight,
|
previewHeight,
|
||||||
previewCam->camera.fov,
|
previewCam->camera.fov,
|
||||||
previewCam->camera.nearClip,
|
previewCam->camera.nearClip,
|
||||||
previewCam->camera.farClip
|
previewCam->camera.farClip,
|
||||||
|
previewCam->camera.applyPostFX
|
||||||
);
|
);
|
||||||
|
|
||||||
if (previewTex != 0) {
|
if (previewTex != 0) {
|
||||||
@@ -4902,7 +5079,7 @@ void Engine::renderProjectBrowserPanel() {
|
|||||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(8.0f, 5.0f));
|
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(8.0f, 5.0f));
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 4.0f));
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 4.0f));
|
||||||
|
|
||||||
ImGui::Begin("Project Manager", &showProjectBrowser);
|
ImGui::Begin("Project Settings", &showProjectBrowser);
|
||||||
|
|
||||||
if (!projectManager.currentProject.isLoaded) {
|
if (!projectManager.currentProject.isLoaded) {
|
||||||
ImGui::TextDisabled("No project loaded");
|
ImGui::TextDisabled("No project loaded");
|
||||||
@@ -4912,7 +5089,7 @@ void Engine::renderProjectBrowserPanel() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::TextColored(ImVec4(0.4f, 0.7f, 0.95f, 1.0f), "[P] %s", projectManager.currentProject.name.c_str());
|
ImGui::TextColored(ImVec4(0.4f, 0.7f, 0.95f, 1.0f), "%s", projectManager.currentProject.name.c_str());
|
||||||
if (projectManager.currentProject.hasUnsavedChanges) {
|
if (projectManager.currentProject.hasUnsavedChanges) {
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::TextColored(ImVec4(1.0f, 0.7f, 0.3f, 1.0f), "*");
|
ImGui::TextColored(ImVec4(1.0f, 0.7f, 0.3f, 1.0f), "*");
|
||||||
@@ -4920,7 +5097,21 @@ void Engine::renderProjectBrowserPanel() {
|
|||||||
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|
||||||
if (ImGui::CollapsingHeader("Scenes", ImGuiTreeNodeFlags_DefaultOpen)) {
|
static int selectedTab = 0;
|
||||||
|
const char* tabs[] = { "Scenes", "Packages", "Assets" };
|
||||||
|
|
||||||
|
ImGui::BeginChild("SettingsNav", ImVec2(180, 0), true);
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
if (ImGui::Selectable(tabs[i], selectedTab == i, 0, ImVec2(0, 32))) {
|
||||||
|
selectedTab = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndChild();
|
||||||
|
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::BeginChild("SettingsBody", ImVec2(0, 0), false);
|
||||||
|
|
||||||
|
if (selectedTab == 0) {
|
||||||
if (ImGui::Button("+ New Scene")) {
|
if (ImGui::Button("+ New Scene")) {
|
||||||
showNewSceneDialog = true;
|
showNewSceneDialog = true;
|
||||||
memset(newSceneName, 0, sizeof(newSceneName));
|
memset(newSceneName, 0, sizeof(newSceneName));
|
||||||
@@ -4962,16 +5153,166 @@ void Engine::renderProjectBrowserPanel() {
|
|||||||
if (scenes.empty()) {
|
if (scenes.empty()) {
|
||||||
ImGui::TextDisabled("No scenes yet");
|
ImGui::TextDisabled("No scenes yet");
|
||||||
}
|
}
|
||||||
}
|
} else if (selectedTab == 1) {
|
||||||
|
static PackageTaskState packageTask;
|
||||||
|
static std::string packageStatus;
|
||||||
|
static char gitUrlBuf[256] = "";
|
||||||
|
static char gitNameBuf[128] = "";
|
||||||
|
static char gitIncludeBuf[128] = "include";
|
||||||
|
|
||||||
if (ImGui::CollapsingHeader("Loaded OBJ Meshes")) {
|
auto pollPackageTask = [&]() {
|
||||||
const auto& meshes = g_objLoader.getAllMeshes();
|
if (!packageTask.active) return;
|
||||||
if (meshes.empty()) {
|
if (!packageTask.future.valid()) {
|
||||||
|
packageTask.active = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto state = packageTask.future.wait_for(std::chrono::milliseconds(0));
|
||||||
|
if (state == std::future_status::ready) {
|
||||||
|
PackageTaskResult result = packageTask.future.get();
|
||||||
|
packageStatus = result.message;
|
||||||
|
packageTask.active = false;
|
||||||
|
packageTask.future = std::future<PackageTaskResult>();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto startPackageTask = [&](const char* label, std::function<PackageTaskResult()> fn) {
|
||||||
|
if (packageTask.active) return;
|
||||||
|
packageTask.active = true;
|
||||||
|
packageTask.label = label;
|
||||||
|
packageTask.startTime = static_cast<float>(ImGui::GetTime());
|
||||||
|
packageTask.future = std::async(std::launch::async, std::move(fn));
|
||||||
|
};
|
||||||
|
|
||||||
|
pollPackageTask();
|
||||||
|
|
||||||
|
if (!packageStatus.empty()) {
|
||||||
|
ImGui::TextWrapped("%s", packageStatus.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packageTask.active) {
|
||||||
|
const ImU32 col = ImGui::GetColorU32(ImGuiCol_ButtonHovered);
|
||||||
|
const ImU32 bg = ImGui::GetColorU32(ImGuiCol_Button);
|
||||||
|
float elapsed = static_cast<float>(ImGui::GetTime()) - packageTask.startTime;
|
||||||
|
float phase = std::fmod(elapsed * 0.25f, 1.0f);
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::Text("%s", packageTask.label.c_str());
|
||||||
|
ImGui::BufferingBar("##pkg_buffer", phase, ImVec2(ImGui::GetContentRegionAvail().x, 6.0f), bg, col);
|
||||||
|
ImGui::Spinner("##pkg_spinner", 10.0f, 4, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::BeginDisabled(packageTask.active);
|
||||||
|
ImGui::TextDisabled("Add package from Git");
|
||||||
|
ImGui::InputTextWithHint("URL", "https://github.com/user/repo.git", gitUrlBuf, sizeof(gitUrlBuf));
|
||||||
|
ImGui::InputTextWithHint("Name (optional)", "use repo name", gitNameBuf, sizeof(gitNameBuf));
|
||||||
|
ImGui::InputTextWithHint("Include dir", "include", gitIncludeBuf, sizeof(gitIncludeBuf));
|
||||||
|
if (ImGui::Button("Add as submodule")) {
|
||||||
|
std::string url = gitUrlBuf;
|
||||||
|
std::string name = gitNameBuf;
|
||||||
|
std::string include = gitIncludeBuf;
|
||||||
|
startPackageTask("Installing package...", [this, url, name, include]() {
|
||||||
|
PackageTaskResult result;
|
||||||
|
std::string newId;
|
||||||
|
if (packageManager.installGitPackage(url, name, include, newId)) {
|
||||||
|
result.success = true;
|
||||||
|
result.message = "Installed package: " + newId;
|
||||||
|
} else {
|
||||||
|
result.message = packageManager.getLastError();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::TextDisabled("Installed packages");
|
||||||
|
if (packageTask.active) {
|
||||||
|
ImGui::TextDisabled("Working...");
|
||||||
|
} else {
|
||||||
|
const auto& registry = packageManager.getRegistry();
|
||||||
|
const auto& installedIds = packageManager.getInstalled();
|
||||||
|
if (installedIds.empty()) {
|
||||||
|
ImGui::TextDisabled("None installed");
|
||||||
|
} else {
|
||||||
|
for (const auto& id : installedIds) {
|
||||||
|
const PackageInfo* pkg = nullptr;
|
||||||
|
for (const auto& p : registry) {
|
||||||
|
if (p.id == id) { pkg = &p; break; }
|
||||||
|
}
|
||||||
|
if (!pkg) continue;
|
||||||
|
|
||||||
|
ImGui::PushID(pkg->id.c_str());
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::Text("%s", pkg->name.c_str());
|
||||||
|
ImGui::TextDisabled("%s", pkg->description.c_str());
|
||||||
|
if (!pkg->external) {
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::TextColored(ImVec4(0.4f, 0.7f, 0.95f, 1.0f), "[bundled]");
|
||||||
|
ImGui::PopID();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::TextDisabled("Path: %s", pkg->localPath.string().c_str());
|
||||||
|
ImGui::TextDisabled("Git: %s", pkg->gitUrl.c_str());
|
||||||
|
|
||||||
|
if (ImGui::Button("Check updates")) {
|
||||||
|
std::string id = pkg->id;
|
||||||
|
startPackageTask("Checking package status...", [this, id]() {
|
||||||
|
PackageTaskResult result;
|
||||||
|
std::string status;
|
||||||
|
if (packageManager.checkGitStatus(id, status)) {
|
||||||
|
result.success = true;
|
||||||
|
result.message = status;
|
||||||
|
} else {
|
||||||
|
result.message = packageManager.getLastError();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Update")) {
|
||||||
|
std::string id = pkg->id;
|
||||||
|
std::string name = pkg->name;
|
||||||
|
startPackageTask("Updating package...", [this, id, name]() {
|
||||||
|
PackageTaskResult result;
|
||||||
|
std::string log;
|
||||||
|
if (packageManager.updateGitPackage(id, log)) {
|
||||||
|
result.success = true;
|
||||||
|
result.message = "Updated " + name + "\n" + log;
|
||||||
|
} else {
|
||||||
|
result.message = packageManager.getLastError();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Uninstall")) {
|
||||||
|
std::string id = pkg->id;
|
||||||
|
std::string name = pkg->name;
|
||||||
|
startPackageTask("Removing package...", [this, id, name]() {
|
||||||
|
PackageTaskResult result;
|
||||||
|
if (packageManager.remove(id)) {
|
||||||
|
result.success = true;
|
||||||
|
result.message = "Removed " + name;
|
||||||
|
} else {
|
||||||
|
result.message = packageManager.getLastError();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ImGui::PopID();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (selectedTab == 2) {
|
||||||
|
ImGui::TextDisabled("Loaded OBJ Meshes");
|
||||||
|
const auto& meshesObj = g_objLoader.getAllMeshes();
|
||||||
|
if (meshesObj.empty()) {
|
||||||
ImGui::TextDisabled("No meshes loaded");
|
ImGui::TextDisabled("No meshes loaded");
|
||||||
ImGui::TextDisabled("Import .obj files from File Browser");
|
ImGui::TextDisabled("Import .obj files from File Browser");
|
||||||
} else {
|
} else {
|
||||||
for (size_t i = 0; i < meshes.size(); i++) {
|
for (size_t i = 0; i < meshesObj.size(); i++) {
|
||||||
const auto& mesh = meshes[i];
|
const auto& mesh = meshesObj[i];
|
||||||
ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_Leaf |
|
ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_Leaf |
|
||||||
ImGuiTreeNodeFlags_SpanAvailWidth |
|
ImGuiTreeNodeFlags_SpanAvailWidth |
|
||||||
ImGuiTreeNodeFlags_NoTreePushOnOpen;
|
ImGuiTreeNodeFlags_NoTreePushOnOpen;
|
||||||
@@ -4989,16 +5330,16 @@ void Engine::renderProjectBrowserPanel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui::CollapsingHeader("Loaded Models (Assimp)")) {
|
ImGui::Separator();
|
||||||
const auto& meshes = getModelLoader().getAllMeshes();
|
ImGui::TextDisabled("Loaded Models (Assimp)");
|
||||||
if (meshes.empty()) {
|
const auto& meshesAssimp = getModelLoader().getAllMeshes();
|
||||||
|
if (meshesAssimp.empty()) {
|
||||||
ImGui::TextDisabled("No models loaded");
|
ImGui::TextDisabled("No models loaded");
|
||||||
ImGui::TextDisabled("Import FBX/GLTF/other supported models from File Browser");
|
ImGui::TextDisabled("Import FBX/GLTF/other supported models from File Browser");
|
||||||
} else {
|
} else {
|
||||||
for (size_t i = 0; i < meshes.size(); i++) {
|
for (size_t i = 0; i < meshesAssimp.size(); i++) {
|
||||||
const auto& mesh = meshes[i];
|
const auto& mesh = meshesAssimp[i];
|
||||||
ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_Leaf |
|
ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_Leaf |
|
||||||
ImGuiTreeNodeFlags_SpanAvailWidth |
|
ImGuiTreeNodeFlags_SpanAvailWidth |
|
||||||
ImGuiTreeNodeFlags_NoTreePushOnOpen;
|
ImGuiTreeNodeFlags_NoTreePushOnOpen;
|
||||||
@@ -5018,11 +5359,12 @@ void Engine::renderProjectBrowserPanel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGui::EndChild();
|
||||||
|
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
ImGui::PopStyleVar(2);
|
ImGui::PopStyleVar(2);
|
||||||
ImGui::PopStyleColor(3);
|
ImGui::PopStyleColor(3);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::renderEnvironmentWindow() {
|
void Engine::renderEnvironmentWindow() {
|
||||||
if (!showEnvironmentWindow) return;
|
if (!showEnvironmentWindow) return;
|
||||||
ImGui::Begin("Environment", &showEnvironmentWindow);
|
ImGui::Begin("Environment", &showEnvironmentWindow);
|
||||||
|
|||||||
481
src/PackageManager.cpp
Normal file
481
src/PackageManager.cpp
Normal file
@@ -0,0 +1,481 @@
|
|||||||
|
#include "PackageManager.h"
|
||||||
|
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <array>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
fs::path normalizePath(const fs::path& p) {
|
||||||
|
std::error_code ec;
|
||||||
|
fs::path canonical = fs::weakly_canonical(p, ec);
|
||||||
|
if (ec) {
|
||||||
|
canonical = fs::absolute(p, ec);
|
||||||
|
}
|
||||||
|
return canonical.lexically_normal();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool containsPath(const std::vector<fs::path>& haystack, const fs::path& needle) {
|
||||||
|
std::string target = normalizePath(needle).string();
|
||||||
|
for (const auto& entry : haystack) {
|
||||||
|
if (normalizePath(entry).string() == target) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::path guessIncludeDir(const fs::path& repoRoot, const std::string& includeRel) {
|
||||||
|
if (!includeRel.empty()) {
|
||||||
|
fs::path candidate = normalizePath(repoRoot / includeRel);
|
||||||
|
if (fs::exists(candidate) && fs::is_directory(candidate)) {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* defaults[] = {"include", "Include", "includes", "inc", "Inc"};
|
||||||
|
for (const char* name : defaults) {
|
||||||
|
fs::path candidate = normalizePath(repoRoot / name);
|
||||||
|
if (fs::exists(candidate) && fs::is_directory(candidate)) {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& entry : fs::directory_iterator(repoRoot)) {
|
||||||
|
if (entry.is_directory()) {
|
||||||
|
return normalizePath(entry.path());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalizePath(repoRoot);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
PackageManager::PackageManager() {
|
||||||
|
buildRegistry();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PackageManager::setProjectRoot(const fs::path& root) {
|
||||||
|
buildRegistry();
|
||||||
|
projectRoot = root;
|
||||||
|
manifestPath = projectRoot / "packages.modu";
|
||||||
|
loadManifest();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PackageManager::isInstalled(const std::string& id) const {
|
||||||
|
return std::find(installedIds.begin(), installedIds.end(), id) != installedIds.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PackageManager::install(const std::string& id) {
|
||||||
|
lastError.clear();
|
||||||
|
const PackageInfo* pkg = findPackage(id);
|
||||||
|
if (!pkg) {
|
||||||
|
lastError = "Unknown package: " + id;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isInstalled(id)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
installedIds.push_back(id);
|
||||||
|
saveManifest();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PackageManager::remove(const std::string& id) {
|
||||||
|
lastError.clear();
|
||||||
|
const PackageInfo* pkg = findPackage(id);
|
||||||
|
if (!pkg) {
|
||||||
|
lastError = "Unknown package: " + id;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (pkg->builtIn) {
|
||||||
|
lastError = "Cannot remove built-in packages.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (pkg->external) {
|
||||||
|
std::string log;
|
||||||
|
std::string deinitCmd = "git submodule deinit -f \"" + pkg->localPath.string() + "\"";
|
||||||
|
runCommand(deinitCmd, log); // best-effort
|
||||||
|
std::string rmCmd = "git rm -f \"" + pkg->localPath.string() + "\"";
|
||||||
|
if (!runCommand(rmCmd, log)) {
|
||||||
|
lastError = "Failed to remove submodule. Git log:\n" + log;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto it = std::remove(installedIds.begin(), installedIds.end(), id);
|
||||||
|
if (it == installedIds.end()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
installedIds.erase(it, installedIds.end());
|
||||||
|
registry.erase(std::remove_if(registry.begin(), registry.end(),
|
||||||
|
[&](const PackageInfo& p){ return p.id == id && p.external; }), registry.end());
|
||||||
|
|
||||||
|
saveManifest();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PackageManager::applyToBuildConfig(ScriptBuildConfig& config) const {
|
||||||
|
std::unordered_set<std::string> defineSet(config.defines.begin(), config.defines.end());
|
||||||
|
std::unordered_set<std::string> linuxLibSet(config.linuxLinkLibs.begin(), config.linuxLinkLibs.end());
|
||||||
|
std::unordered_set<std::string> winLibSet(config.windowsLinkLibs.begin(), config.windowsLinkLibs.end());
|
||||||
|
|
||||||
|
for (const auto& id : installedIds) {
|
||||||
|
const PackageInfo* pkg = findPackage(id);
|
||||||
|
if (!pkg) continue;
|
||||||
|
|
||||||
|
for (const auto& dir : pkg->includeDirs) {
|
||||||
|
if (!containsPath(config.includeDirs, dir)) {
|
||||||
|
config.includeDirs.push_back(normalizePath(dir));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto& def : pkg->defines) {
|
||||||
|
if (defineSet.insert(def).second) {
|
||||||
|
config.defines.push_back(def);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto& lib : pkg->linuxLibs) {
|
||||||
|
if (linuxLibSet.insert(lib).second) {
|
||||||
|
config.linuxLinkLibs.push_back(lib);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto& lib : pkg->windowsLibs) {
|
||||||
|
if (winLibSet.insert(lib).second) {
|
||||||
|
config.windowsLinkLibs.push_back(lib);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PackageManager::buildRegistry() {
|
||||||
|
registry.clear();
|
||||||
|
fs::path engineRoot = fs::current_path();
|
||||||
|
|
||||||
|
auto add = [this](PackageInfo info) {
|
||||||
|
for (auto& dir : info.includeDirs) {
|
||||||
|
dir = normalizePath(dir);
|
||||||
|
}
|
||||||
|
registry.push_back(std::move(info));
|
||||||
|
};
|
||||||
|
|
||||||
|
PackageInfo engineCore;
|
||||||
|
engineCore.id = "engine-core";
|
||||||
|
engineCore.name = "Engine Core";
|
||||||
|
engineCore.description = "Modularity engine headers and common utilities";
|
||||||
|
engineCore.builtIn = true;
|
||||||
|
engineCore.includeDirs = {
|
||||||
|
engineRoot / "src",
|
||||||
|
engineRoot / "include",
|
||||||
|
engineRoot / "src/ThirdParty",
|
||||||
|
engineRoot / "src/ThirdParty/glm",
|
||||||
|
engineRoot / "src/ThirdParty/glad"
|
||||||
|
};
|
||||||
|
engineCore.linuxLibs = {"pthread", "dl"};
|
||||||
|
engineCore.windowsLibs = {"User32.lib", "Advapi32.lib"};
|
||||||
|
add(engineCore);
|
||||||
|
|
||||||
|
PackageInfo glm;
|
||||||
|
glm.id = "glm";
|
||||||
|
glm.name = "GLM Math";
|
||||||
|
glm.description = "Header-only GLM math library (bundled)";
|
||||||
|
glm.builtIn = false; // Count as installed instead of hidden built-in
|
||||||
|
glm.includeDirs = { engineRoot / "src/ThirdParty/glm" };
|
||||||
|
add(glm);
|
||||||
|
|
||||||
|
PackageInfo imgui;
|
||||||
|
imgui.id = "imgui";
|
||||||
|
imgui.name = "Dear ImGui";
|
||||||
|
imgui.description = "Immediate-mode UI helpers for editor-time tools";
|
||||||
|
imgui.builtIn = false;
|
||||||
|
imgui.includeDirs = {
|
||||||
|
engineRoot / "src/ThirdParty/imgui",
|
||||||
|
engineRoot / "src/ThirdParty/imgui/backends"
|
||||||
|
};
|
||||||
|
add(imgui);
|
||||||
|
|
||||||
|
PackageInfo imguizmo;
|
||||||
|
imguizmo.id = "imguizmo";
|
||||||
|
imguizmo.name = "ImGuizmo";
|
||||||
|
imguizmo.description = "Gizmo/transform helpers used by the editor";
|
||||||
|
imguizmo.builtIn = false;
|
||||||
|
imguizmo.includeDirs = { engineRoot / "src/ThirdParty/ImGuizmo" };
|
||||||
|
add(imguizmo);
|
||||||
|
|
||||||
|
PackageInfo miniaudio;
|
||||||
|
miniaudio.id = "miniaudio";
|
||||||
|
miniaudio.name = "miniaudio";
|
||||||
|
miniaudio.description = "Single-header audio helpers (bundled)";
|
||||||
|
miniaudio.builtIn = false;
|
||||||
|
miniaudio.includeDirs = { engineRoot / "include/ThirdParty" };
|
||||||
|
add(miniaudio);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PackageManager::loadManifest() {
|
||||||
|
installedIds.clear();
|
||||||
|
for (const auto& pkg : registry) {
|
||||||
|
if (!isInstalled(pkg.id)) {
|
||||||
|
installedIds.push_back(pkg.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (manifestPath.empty()) return;
|
||||||
|
if (!fs::exists(manifestPath)) {
|
||||||
|
saveManifest();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ifstream file(manifestPath);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
lastError = "Unable to open package manifest.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(file, line)) {
|
||||||
|
std::string cleaned = trim(line);
|
||||||
|
if (cleaned.empty() || cleaned[0] == '#') continue;
|
||||||
|
|
||||||
|
std::string id;
|
||||||
|
if (cleaned.rfind("package=", 0) == 0) {
|
||||||
|
id = cleaned.substr(8);
|
||||||
|
if (!id.empty() && !isInstalled(id) && findPackage(id)) {
|
||||||
|
installedIds.push_back(id);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cleaned.rfind("git=", 0) == 0) {
|
||||||
|
auto payload = cleaned.substr(4);
|
||||||
|
auto parts = split(payload, '|');
|
||||||
|
if (parts.size() < 4) continue;
|
||||||
|
PackageInfo pkg;
|
||||||
|
pkg.id = parts[0];
|
||||||
|
pkg.name = parts[1];
|
||||||
|
pkg.description = "External package from git";
|
||||||
|
pkg.external = true;
|
||||||
|
pkg.gitUrl = parts[2];
|
||||||
|
fs::path relPath = parts[3];
|
||||||
|
pkg.localPath = normalizePath(projectRoot / relPath);
|
||||||
|
|
||||||
|
std::vector<std::string> includeTokens;
|
||||||
|
if (parts.size() > 4) includeTokens = split(parts[4], ';');
|
||||||
|
for (const auto& inc : includeTokens) {
|
||||||
|
if (inc.empty()) continue;
|
||||||
|
pkg.includeDirs.push_back(normalizePath(projectRoot / inc));
|
||||||
|
}
|
||||||
|
std::vector<std::string> defTokens;
|
||||||
|
if (parts.size() > 5) defTokens = split(parts[5], ';');
|
||||||
|
pkg.defines = defTokens;
|
||||||
|
if (parts.size() > 6) pkg.linuxLibs = split(parts[6], ';');
|
||||||
|
if (parts.size() > 7) pkg.windowsLibs = split(parts[7], ';');
|
||||||
|
|
||||||
|
registry.push_back(pkg);
|
||||||
|
if (!isInstalled(pkg.id)) {
|
||||||
|
installedIds.push_back(pkg.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PackageManager::saveManifest() const {
|
||||||
|
if (manifestPath.empty()) return;
|
||||||
|
std::ofstream file(manifestPath);
|
||||||
|
if (!file.is_open()) return;
|
||||||
|
|
||||||
|
file << "# Modularity package manifest\n";
|
||||||
|
file << "# Add optional script-time dependencies here\n";
|
||||||
|
for (const auto& id : installedIds) {
|
||||||
|
const PackageInfo* pkg = findPackage(id);
|
||||||
|
if (!pkg) continue;
|
||||||
|
if (!pkg->external) {
|
||||||
|
if (!pkg->builtIn) {
|
||||||
|
file << "package=" << id << "\n";
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Persist external package metadata
|
||||||
|
std::vector<std::string> relIncludes;
|
||||||
|
for (const auto& inc : pkg->includeDirs) {
|
||||||
|
std::error_code ec;
|
||||||
|
fs::path rel = fs::relative(inc, projectRoot, ec);
|
||||||
|
relIncludes.push_back((!ec ? rel : inc).generic_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
file << "git=" << pkg->id << "|"
|
||||||
|
<< pkg->name << "|"
|
||||||
|
<< pkg->gitUrl << "|";
|
||||||
|
|
||||||
|
std::error_code ec;
|
||||||
|
fs::path relPath = fs::relative(pkg->localPath, projectRoot, ec);
|
||||||
|
file << ((!ec ? relPath : pkg->localPath).generic_string()) << "|";
|
||||||
|
file << join(relIncludes, ';') << "|";
|
||||||
|
file << join(pkg->defines, ';') << "|";
|
||||||
|
file << join(pkg->linuxLibs, ';') << "|";
|
||||||
|
file << join(pkg->windowsLibs, ';') << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const PackageInfo* PackageManager::findPackage(const std::string& id) const {
|
||||||
|
auto it = std::find_if(registry.begin(), registry.end(), [&](const PackageInfo& p) {
|
||||||
|
return p.id == id;
|
||||||
|
});
|
||||||
|
return it == registry.end() ? nullptr : &(*it);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PackageManager::isBuiltIn(const std::string& id) const {
|
||||||
|
const PackageInfo* pkg = findPackage(id);
|
||||||
|
return pkg && pkg->builtIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string PackageManager::trim(const std::string& value) {
|
||||||
|
size_t start = 0;
|
||||||
|
while (start < value.size() && std::isspace(static_cast<unsigned char>(value[start]))) start++;
|
||||||
|
size_t end = value.size();
|
||||||
|
while (end > start && std::isspace(static_cast<unsigned char>(value[end - 1]))) end--;
|
||||||
|
return value.substr(start, end - start);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string PackageManager::slugify(const std::string& value) {
|
||||||
|
std::string out;
|
||||||
|
out.reserve(value.size());
|
||||||
|
for (char c : value) {
|
||||||
|
if (std::isalnum(static_cast<unsigned char>(c))) {
|
||||||
|
out.push_back(static_cast<char>(std::tolower(static_cast<unsigned char>(c))));
|
||||||
|
} else if (c == '-' || c == '_') {
|
||||||
|
out.push_back(c);
|
||||||
|
} else if (std::isspace(static_cast<unsigned char>(c))) {
|
||||||
|
out.push_back('-');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (out.empty()) out = "pkg";
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PackageManager::runCommand(const std::string& command, std::string& output) {
|
||||||
|
std::array<char, 256> buffer{};
|
||||||
|
FILE* pipe = popen(command.c_str(), "r");
|
||||||
|
if (!pipe) return false;
|
||||||
|
while (fgets(buffer.data(), buffer.size(), pipe) != nullptr) {
|
||||||
|
output += buffer.data();
|
||||||
|
}
|
||||||
|
int rc = pclose(pipe);
|
||||||
|
return rc == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PackageManager::ensureProjectRoot() const {
|
||||||
|
return !projectRoot.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> PackageManager::split(const std::string& input, char delim) const {
|
||||||
|
std::vector<std::string> out;
|
||||||
|
std::stringstream ss(input);
|
||||||
|
std::string part;
|
||||||
|
while (std::getline(ss, part, delim)) {
|
||||||
|
out.push_back(part);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string PackageManager::join(const std::vector<std::string>& vals, char delim) const {
|
||||||
|
std::ostringstream oss;
|
||||||
|
for (size_t i = 0; i < vals.size(); ++i) {
|
||||||
|
if (i > 0) oss << delim;
|
||||||
|
oss << vals[i];
|
||||||
|
}
|
||||||
|
return oss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::path PackageManager::packagesFolder() const {
|
||||||
|
return projectRoot / "Packages";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PackageManager::installGitPackage(const std::string& url,
|
||||||
|
const std::string& nameHint,
|
||||||
|
const std::string& includeRel,
|
||||||
|
std::string& outId) {
|
||||||
|
lastError.clear();
|
||||||
|
if (!ensureProjectRoot()) {
|
||||||
|
lastError = "Project root not set.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (url.empty()) {
|
||||||
|
lastError = "Git URL is required.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string repoName = nameHint;
|
||||||
|
size_t slash = url.find_last_of("/\\");
|
||||||
|
if (repoName.empty() && slash != std::string::npos) {
|
||||||
|
repoName = url.substr(slash + 1);
|
||||||
|
if (repoName.rfind(".git") != std::string::npos) {
|
||||||
|
repoName = repoName.substr(0, repoName.size() - 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::string id = slugify(repoName);
|
||||||
|
if (isInstalled(id)) {
|
||||||
|
lastError = "Package already installed: " + id;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::path dest = normalizePath(packagesFolder() / id);
|
||||||
|
fs::create_directories(dest.parent_path());
|
||||||
|
|
||||||
|
std::string cmd = "git submodule add --force \"" + url + "\" \"" + dest.string() + "\"";
|
||||||
|
std::string log;
|
||||||
|
if (!runCommand(cmd, log)) {
|
||||||
|
lastError = "git submodule add failed:\n" + log;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PackageInfo pkg;
|
||||||
|
pkg.id = id;
|
||||||
|
pkg.name = repoName.empty() ? id : repoName;
|
||||||
|
pkg.description = "External package from " + url;
|
||||||
|
pkg.external = true;
|
||||||
|
pkg.gitUrl = url;
|
||||||
|
pkg.localPath = dest;
|
||||||
|
pkg.includeDirs.push_back(guessIncludeDir(dest, includeRel));
|
||||||
|
|
||||||
|
registry.push_back(pkg);
|
||||||
|
installedIds.push_back(id);
|
||||||
|
saveManifest();
|
||||||
|
outId = id;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PackageManager::checkGitStatus(const std::string& id, std::string& outStatus) {
|
||||||
|
lastError.clear();
|
||||||
|
outStatus.clear();
|
||||||
|
const PackageInfo* pkg = findPackage(id);
|
||||||
|
if (!pkg || !pkg->external) {
|
||||||
|
lastError = "Package is not external or not found.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string fetchCmd = "git -C \"" + pkg->localPath.string() + "\" fetch --quiet";
|
||||||
|
runCommand(fetchCmd, outStatus); // ignore fetch failures here
|
||||||
|
|
||||||
|
std::string statusCmd = "git -C \"" + pkg->localPath.string() + "\" status -sb";
|
||||||
|
if (!runCommand(statusCmd, outStatus)) {
|
||||||
|
lastError = "Failed to read git status.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PackageManager::updateGitPackage(const std::string& id, std::string& outLog) {
|
||||||
|
lastError.clear();
|
||||||
|
outLog.clear();
|
||||||
|
const PackageInfo* pkg = findPackage(id);
|
||||||
|
if (!pkg || !pkg->external) {
|
||||||
|
lastError = "Package is not external or not found.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::string cmd = "git -C \"" + pkg->localPath.string() + "\" pull --ff-only";
|
||||||
|
if (!runCommand(cmd, outLog)) {
|
||||||
|
lastError = "git pull failed.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
64
src/PackageManager.h
Normal file
64
src/PackageManager.h
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Common.h"
|
||||||
|
#include "ScriptCompiler.h"
|
||||||
|
#include <cctype>
|
||||||
|
|
||||||
|
struct PackageInfo {
|
||||||
|
std::string id;
|
||||||
|
std::string name;
|
||||||
|
std::string description;
|
||||||
|
bool builtIn = false;
|
||||||
|
bool external = false;
|
||||||
|
std::string gitUrl;
|
||||||
|
fs::path localPath; // Absolute path for external packages
|
||||||
|
fs::path includeHint; // Absolute include root for external packages
|
||||||
|
std::vector<fs::path> includeDirs;
|
||||||
|
std::vector<std::string> defines;
|
||||||
|
std::vector<std::string> linuxLibs;
|
||||||
|
std::vector<std::string> windowsLibs;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Minimal package manager for script dependencies. Keeps a small registry of
|
||||||
|
// known/bundled packages and a per-project manifest of which optional ones are
|
||||||
|
// enabled. Installed packages augment the script build config (include dirs,
|
||||||
|
// defines, link libs) before compilation.
|
||||||
|
class PackageManager {
|
||||||
|
public:
|
||||||
|
PackageManager();
|
||||||
|
|
||||||
|
void setProjectRoot(const fs::path& root);
|
||||||
|
const std::vector<PackageInfo>& getRegistry() const { return registry; }
|
||||||
|
const std::vector<std::string>& getInstalled() const { return installedIds; }
|
||||||
|
bool isInstalled(const std::string& id) const;
|
||||||
|
bool install(const std::string& id);
|
||||||
|
bool installGitPackage(const std::string& url,
|
||||||
|
const std::string& nameHint,
|
||||||
|
const std::string& includeRel,
|
||||||
|
std::string& outId);
|
||||||
|
bool checkGitStatus(const std::string& id, std::string& outStatus);
|
||||||
|
bool updateGitPackage(const std::string& id, std::string& outLog);
|
||||||
|
bool remove(const std::string& id);
|
||||||
|
void applyToBuildConfig(ScriptBuildConfig& config) const;
|
||||||
|
const std::string& getLastError() const { return lastError; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void buildRegistry();
|
||||||
|
void loadManifest();
|
||||||
|
void saveManifest() const;
|
||||||
|
const PackageInfo* findPackage(const std::string& id) const;
|
||||||
|
bool isBuiltIn(const std::string& id) const;
|
||||||
|
static std::string trim(const std::string& value);
|
||||||
|
static std::string slugify(const std::string& value);
|
||||||
|
static bool runCommand(const std::string& command, std::string& output);
|
||||||
|
bool ensureProjectRoot() const;
|
||||||
|
std::vector<std::string> split(const std::string& input, char delim) const;
|
||||||
|
std::string join(const std::vector<std::string>& vals, char delim) const;
|
||||||
|
fs::path packagesFolder() const;
|
||||||
|
|
||||||
|
fs::path projectRoot;
|
||||||
|
fs::path manifestPath;
|
||||||
|
std::vector<PackageInfo> registry;
|
||||||
|
std::vector<std::string> installedIds;
|
||||||
|
std::string lastError;
|
||||||
|
};
|
||||||
@@ -352,6 +352,7 @@ bool SceneSerializer::saveScene(const fs::path& filePath,
|
|||||||
file << "cameraFov=" << obj.camera.fov << "\n";
|
file << "cameraFov=" << obj.camera.fov << "\n";
|
||||||
file << "cameraNear=" << obj.camera.nearClip << "\n";
|
file << "cameraNear=" << obj.camera.nearClip << "\n";
|
||||||
file << "cameraFar=" << obj.camera.farClip << "\n";
|
file << "cameraFar=" << obj.camera.farClip << "\n";
|
||||||
|
file << "cameraPostFX=" << (obj.camera.applyPostFX ? 1 : 0) << "\n";
|
||||||
if (obj.type == ObjectType::PostFXNode) {
|
if (obj.type == ObjectType::PostFXNode) {
|
||||||
file << "postEnabled=" << (obj.postFx.enabled ? 1 : 0) << "\n";
|
file << "postEnabled=" << (obj.postFx.enabled ? 1 : 0) << "\n";
|
||||||
file << "postBloomEnabled=" << (obj.postFx.bloomEnabled ? 1 : 0) << "\n";
|
file << "postBloomEnabled=" << (obj.postFx.bloomEnabled ? 1 : 0) << "\n";
|
||||||
@@ -647,6 +648,8 @@ bool SceneSerializer::loadScene(const fs::path& filePath,
|
|||||||
currentObj->camera.nearClip = std::stof(value);
|
currentObj->camera.nearClip = std::stof(value);
|
||||||
} else if (key == "cameraFar") {
|
} else if (key == "cameraFar") {
|
||||||
currentObj->camera.farClip = std::stof(value);
|
currentObj->camera.farClip = std::stof(value);
|
||||||
|
} else if (key == "cameraPostFX") {
|
||||||
|
currentObj->camera.applyPostFX = (std::stoi(value) != 0);
|
||||||
} else if (key == "postEnabled") {
|
} else if (key == "postEnabled") {
|
||||||
currentObj->postFx.enabled = (std::stoi(value) != 0);
|
currentObj->postFx.enabled = (std::stoi(value) != 0);
|
||||||
} else if (key == "postBloomEnabled") {
|
} else if (key == "postBloomEnabled") {
|
||||||
|
|||||||
@@ -1072,7 +1072,7 @@ PostFXSettings Renderer::gatherPostFX(const std::vector<SceneObject>& sceneObjec
|
|||||||
return combined;
|
return combined;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::applyPostProcessing(const std::vector<SceneObject>& sceneObjects) {
|
unsigned int Renderer::applyPostProcessing(const std::vector<SceneObject>& sceneObjects, unsigned int sourceTexture, int width, int height, bool allowHistory) {
|
||||||
PostFXSettings settings = gatherPostFX(sceneObjects);
|
PostFXSettings settings = gatherPostFX(sceneObjects);
|
||||||
GLint polygonMode[2] = { GL_FILL, GL_FILL };
|
GLint polygonMode[2] = { GL_FILL, GL_FILL };
|
||||||
#ifdef GL_POLYGON_MODE
|
#ifdef GL_POLYGON_MODE
|
||||||
@@ -1088,18 +1088,25 @@ void Renderer::applyPostProcessing(const std::vector<SceneObject>& sceneObjects)
|
|||||||
wantsEffects = false;
|
wantsEffects = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!wantsEffects || !postShader || currentWidth <= 0 || currentHeight <= 0) {
|
if (!wantsEffects || !postShader || width <= 0 || height <= 0 || sourceTexture == 0) {
|
||||||
displayTexture = viewportTexture;
|
if (allowHistory) {
|
||||||
clearHistory();
|
displayTexture = sourceTexture;
|
||||||
return;
|
clearHistory();
|
||||||
|
}
|
||||||
|
return sourceTexture;
|
||||||
}
|
}
|
||||||
|
|
||||||
ensureRenderTarget(postTarget, currentWidth, currentHeight);
|
RenderTarget& target = allowHistory ? postTarget : previewPostTarget;
|
||||||
ensureRenderTarget(historyTarget, currentWidth, currentHeight);
|
ensureRenderTarget(target, width, height);
|
||||||
if (postTarget.fbo == 0 || postTarget.texture == 0) {
|
if (allowHistory) {
|
||||||
displayTexture = viewportTexture;
|
ensureRenderTarget(historyTarget, width, height);
|
||||||
clearHistory();
|
}
|
||||||
return;
|
if (target.fbo == 0 || target.texture == 0) {
|
||||||
|
if (allowHistory) {
|
||||||
|
displayTexture = sourceTexture;
|
||||||
|
clearHistory();
|
||||||
|
}
|
||||||
|
return sourceTexture;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Bloom using bright pass + separable blur (inspired by ProcessingPostFX) ---
|
// --- Bloom using bright pass + separable blur (inspired by ProcessingPostFX) ---
|
||||||
@@ -1110,9 +1117,9 @@ void Renderer::applyPostProcessing(const std::vector<SceneObject>& sceneObjects)
|
|||||||
brightShader->use();
|
brightShader->use();
|
||||||
brightShader->setFloat("threshold", settings.bloomThreshold);
|
brightShader->setFloat("threshold", settings.bloomThreshold);
|
||||||
glActiveTexture(GL_TEXTURE0);
|
glActiveTexture(GL_TEXTURE0);
|
||||||
glBindTexture(GL_TEXTURE_2D, viewportTexture);
|
glBindTexture(GL_TEXTURE_2D, sourceTexture);
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, bloomTargetA.fbo);
|
glBindFramebuffer(GL_FRAMEBUFFER, bloomTargetA.fbo);
|
||||||
glViewport(0, 0, currentWidth, currentHeight);
|
glViewport(0, 0, width, height);
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
drawFullscreenQuad();
|
drawFullscreenQuad();
|
||||||
|
|
||||||
@@ -1128,11 +1135,11 @@ void Renderer::applyPostProcessing(const std::vector<SceneObject>& sceneObjects)
|
|||||||
RenderTarget* writeTarget = &bloomTargetB;
|
RenderTarget* writeTarget = &bloomTargetB;
|
||||||
for (int i = 0; i < 4; ++i) {
|
for (int i = 0; i < 4; ++i) {
|
||||||
blurShader->setBool("horizontal", horizontal);
|
blurShader->setBool("horizontal", horizontal);
|
||||||
blurShader->setVec2("texelSize", glm::vec2(1.0f / currentWidth, 1.0f / currentHeight));
|
blurShader->setVec2("texelSize", glm::vec2(1.0f / width, 1.0f / height));
|
||||||
glActiveTexture(GL_TEXTURE0);
|
glActiveTexture(GL_TEXTURE0);
|
||||||
glBindTexture(GL_TEXTURE_2D, pingTex);
|
glBindTexture(GL_TEXTURE_2D, pingTex);
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, writeTarget->fbo);
|
glBindFramebuffer(GL_FRAMEBUFFER, writeTarget->fbo);
|
||||||
glViewport(0, 0, currentWidth, currentHeight);
|
glViewport(0, 0, width, height);
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
drawFullscreenQuad();
|
drawFullscreenQuad();
|
||||||
|
|
||||||
@@ -1161,7 +1168,7 @@ void Renderer::applyPostProcessing(const std::vector<SceneObject>& sceneObjects)
|
|||||||
postShader->setVec3("colorFilter", settings.colorFilter);
|
postShader->setVec3("colorFilter", settings.colorFilter);
|
||||||
postShader->setBool("enableMotionBlur", settings.motionBlurEnabled);
|
postShader->setBool("enableMotionBlur", settings.motionBlurEnabled);
|
||||||
postShader->setFloat("motionBlurStrength", settings.motionBlurStrength);
|
postShader->setFloat("motionBlurStrength", settings.motionBlurStrength);
|
||||||
postShader->setBool("hasHistory", historyValid);
|
postShader->setBool("hasHistory", allowHistory && historyValid);
|
||||||
postShader->setBool("enableVignette", settings.vignetteEnabled);
|
postShader->setBool("enableVignette", settings.vignetteEnabled);
|
||||||
postShader->setFloat("vignetteIntensity", settings.vignetteIntensity);
|
postShader->setFloat("vignetteIntensity", settings.vignetteIntensity);
|
||||||
postShader->setFloat("vignetteSmoothness", settings.vignetteSmoothness);
|
postShader->setFloat("vignetteSmoothness", settings.vignetteSmoothness);
|
||||||
@@ -1172,39 +1179,44 @@ void Renderer::applyPostProcessing(const std::vector<SceneObject>& sceneObjects)
|
|||||||
postShader->setFloat("aoStrength", settings.aoStrength);
|
postShader->setFloat("aoStrength", settings.aoStrength);
|
||||||
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
glActiveTexture(GL_TEXTURE0);
|
||||||
glBindTexture(GL_TEXTURE_2D, viewportTexture);
|
glBindTexture(GL_TEXTURE_2D, sourceTexture);
|
||||||
glActiveTexture(GL_TEXTURE1);
|
glActiveTexture(GL_TEXTURE1);
|
||||||
glBindTexture(GL_TEXTURE_2D, bloomTex ? bloomTex : viewportTexture);
|
glBindTexture(GL_TEXTURE_2D, bloomTex ? bloomTex : sourceTexture);
|
||||||
glActiveTexture(GL_TEXTURE2);
|
glActiveTexture(GL_TEXTURE2);
|
||||||
glBindTexture(GL_TEXTURE_2D, historyTarget.texture);
|
glBindTexture(GL_TEXTURE_2D, allowHistory ? historyTarget.texture : 0);
|
||||||
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, postTarget.fbo);
|
glBindFramebuffer(GL_FRAMEBUFFER, target.fbo);
|
||||||
glViewport(0, 0, currentWidth, currentHeight);
|
glViewport(0, 0, width, height);
|
||||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
drawFullscreenQuad();
|
drawFullscreenQuad();
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
glEnable(GL_DEPTH_TEST);
|
glEnable(GL_DEPTH_TEST);
|
||||||
|
|
||||||
displayTexture = postTarget.texture;
|
if (allowHistory) {
|
||||||
|
displayTexture = target.texture;
|
||||||
|
}
|
||||||
|
|
||||||
if (settings.motionBlurEnabled && historyTarget.fbo != 0) {
|
if (settings.motionBlurEnabled && allowHistory && historyTarget.fbo != 0) {
|
||||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, postTarget.fbo);
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, target.fbo);
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, historyTarget.fbo);
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, historyTarget.fbo);
|
||||||
glBlitFramebuffer(0, 0, currentWidth, currentHeight, 0, 0, currentWidth, currentHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
historyValid = true;
|
historyValid = true;
|
||||||
} else {
|
} else if (allowHistory) {
|
||||||
clearHistory();
|
clearHistory();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return target.texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::renderScene(const Camera& camera, const std::vector<SceneObject>& sceneObjects, int /*selectedId*/, float fovDeg, float nearPlane, float farPlane) {
|
void Renderer::renderScene(const Camera& camera, const std::vector<SceneObject>& sceneObjects, int /*selectedId*/, float fovDeg, float nearPlane, float farPlane) {
|
||||||
renderSceneInternal(camera, sceneObjects, currentWidth, currentHeight, true, fovDeg, nearPlane, farPlane);
|
renderSceneInternal(camera, sceneObjects, currentWidth, currentHeight, true, fovDeg, nearPlane, farPlane);
|
||||||
applyPostProcessing(sceneObjects);
|
unsigned int result = applyPostProcessing(sceneObjects, viewportTexture, currentWidth, currentHeight, true);
|
||||||
|
displayTexture = result ? result : viewportTexture;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int Renderer::renderScenePreview(const Camera& camera, const std::vector<SceneObject>& sceneObjects, int width, int height, float fovDeg, float nearPlane, float farPlane) {
|
unsigned int Renderer::renderScenePreview(const Camera& camera, const std::vector<SceneObject>& sceneObjects, int width, int height, float fovDeg, float nearPlane, float farPlane, bool applyPostFX) {
|
||||||
ensureRenderTarget(previewTarget, width, height);
|
ensureRenderTarget(previewTarget, width, height);
|
||||||
if (previewTarget.fbo == 0) return 0;
|
if (previewTarget.fbo == 0) return 0;
|
||||||
|
|
||||||
@@ -1214,7 +1226,11 @@ unsigned int Renderer::renderScenePreview(const Camera& camera, const std::vecto
|
|||||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||||
|
|
||||||
renderSceneInternal(camera, sceneObjects, width, height, true, fovDeg, nearPlane, farPlane);
|
renderSceneInternal(camera, sceneObjects, width, height, true, fovDeg, nearPlane, farPlane);
|
||||||
return previewTarget.texture;
|
if (!applyPostFX) {
|
||||||
|
return previewTarget.texture;
|
||||||
|
}
|
||||||
|
unsigned int processed = applyPostProcessing(sceneObjects, previewTarget.texture, width, height, false);
|
||||||
|
return processed ? processed : previewTarget.texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::endRender() {
|
void Renderer::endRender() {
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ private:
|
|||||||
};
|
};
|
||||||
RenderTarget previewTarget;
|
RenderTarget previewTarget;
|
||||||
RenderTarget postTarget;
|
RenderTarget postTarget;
|
||||||
|
RenderTarget previewPostTarget;
|
||||||
RenderTarget historyTarget;
|
RenderTarget historyTarget;
|
||||||
RenderTarget bloomTargetA;
|
RenderTarget bloomTargetA;
|
||||||
RenderTarget bloomTargetB;
|
RenderTarget bloomTargetB;
|
||||||
@@ -112,7 +113,7 @@ private:
|
|||||||
void clearHistory();
|
void clearHistory();
|
||||||
void clearTarget(RenderTarget& target);
|
void clearTarget(RenderTarget& target);
|
||||||
void renderSceneInternal(const Camera& camera, const std::vector<SceneObject>& sceneObjects, int width, int height, bool unbindFramebuffer, float fovDeg, float nearPlane, float farPlane);
|
void renderSceneInternal(const Camera& camera, const std::vector<SceneObject>& sceneObjects, int width, int height, bool unbindFramebuffer, float fovDeg, float nearPlane, float farPlane);
|
||||||
void applyPostProcessing(const std::vector<SceneObject>& sceneObjects);
|
unsigned int applyPostProcessing(const std::vector<SceneObject>& sceneObjects, unsigned int sourceTexture, int width, int height, bool allowHistory);
|
||||||
PostFXSettings gatherPostFX(const std::vector<SceneObject>& sceneObjects) const;
|
PostFXSettings gatherPostFX(const std::vector<SceneObject>& sceneObjects) const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -133,7 +134,7 @@ public:
|
|||||||
void renderSkybox(const glm::mat4& view, const glm::mat4& proj);
|
void renderSkybox(const glm::mat4& view, const glm::mat4& proj);
|
||||||
void renderObject(const SceneObject& obj);
|
void renderObject(const SceneObject& obj);
|
||||||
void renderScene(const Camera& camera, const std::vector<SceneObject>& sceneObjects, int selectedId = -1, float fovDeg = FOV, float nearPlane = NEAR_PLANE, float farPlane = FAR_PLANE);
|
void renderScene(const Camera& camera, const std::vector<SceneObject>& sceneObjects, int selectedId = -1, float fovDeg = FOV, float nearPlane = NEAR_PLANE, float farPlane = FAR_PLANE);
|
||||||
unsigned int renderScenePreview(const Camera& camera, const std::vector<SceneObject>& sceneObjects, int width, int height, float fovDeg, float nearPlane, float farPlane);
|
unsigned int renderScenePreview(const Camera& camera, const std::vector<SceneObject>& sceneObjects, int width, int height, float fovDeg, float nearPlane, float farPlane, bool applyPostFX = false);
|
||||||
void endRender();
|
void endRender();
|
||||||
|
|
||||||
Skybox* getSkybox() { return skybox; }
|
Skybox* getSkybox() { return skybox; }
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ struct CameraComponent {
|
|||||||
float fov = FOV;
|
float fov = FOV;
|
||||||
float nearClip = NEAR_PLANE;
|
float nearClip = NEAR_PLANE;
|
||||||
float farClip = FAR_PLANE;
|
float farClip = FAR_PLANE;
|
||||||
|
bool applyPostFX = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PostFXSettings {
|
struct PostFXSettings {
|
||||||
|
|||||||
Reference in New Issue
Block a user