3#include <nlohmann/json.hpp>
15 #include <sys/types.h>
27 std::filesystem::path defaultSave = docs /
"VexProjects";
29 std::string pathStr = defaultSave.string();
30 if (pathStr.length() <
sizeof(m_newProjParentDir)) {
31 strcpy(m_newProjParentDir, pathStr.c_str());
54 if (!m_interface->getRenderer().beginFrame(m_resolutionManager->getWindowResolution(), renderData)) {
58 renderData.imguiTextureID = m_interface->getRenderer().getImGuiTextureID(*m_imgui);
60 m_imgui->beginFrame();
61 m_imgui->executeUIFunctions();
70 m_interface->getRenderer().composeFrame(renderData, *m_imgui,
true);
71 m_interface->getRenderer().endFrame(renderData);
73 }
catch (
const std::exception& e) {
74 log(LogLevel::ERROR,
"Selector render failed: %s", e.what());
80 ImGuiViewport* viewport = ImGui::GetMainViewport();
81 ImGui::SetNextWindowPos(viewport->Pos);
82 ImGui::SetNextWindowSize(viewport->Size);
83 ImGui::SetNextWindowViewport(viewport->ID);
85 ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove |
86 ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoBringToFrontOnFocus |
87 ImGuiWindowFlags_NoNavFocus;
89 ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
90 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(20.0f, 20.0f));
92 ImGui::Begin(
"SelectorSpace",
nullptr, windowFlags);
94 ImGui::Text(
"VEX ENGINE - PROJECTS");
97 if (ImGui::Button(
"+ Create New Project", ImVec2(200, 30))) {
98 m_showCreatorModal =
true;
101 if (ImGui::Button(
"+ Add Existing Project", ImVec2(200, 30))) {
102 m_showAddExistingModal =
true;
103 memset(m_addExistingBuffer, 0,
sizeof(m_addExistingBuffer));
108 ImGui::Dummy(ImVec2(0.0f, 10.0f));
110 float cardWidth = 220.0f;
111 int columns =
static_cast<int>(viewport->Size.x / cardWidth);
112 if (columns < 1) columns = 1;
114 if (ImGui::BeginTable(
"ProjectsGrid", columns)) {
115 if(m_projects.size() < 1){
116 ImGui::TableNextColumn();
117 ImGui::Text(
"No projects yet.");
119 for (
int i = 0; i < m_projects.size(); ++i) {
120 ImGui::TableNextColumn();
122 ImGui::PushID(m_projects[i].path.c_str());
123 if (ImGui::Button(m_projects[i].name.c_str(), ImVec2(200, 300))) {
127 if (ImGui::BeginPopupContextItem()) {
128 ImGui::TextDisabled(
"%s", m_projects[i].name.c_str());
131 if (ImGui::MenuItem(
"Move Project Folder...")) {
132 m_contextMenuTargetIndex = i;
133 m_showMoveModal =
true;
135 std::filesystem::path p(m_projects[i].path);
136 std::string parent = p.parent_path().string();
137 strcpy(m_moveProjDestBuffer, parent.c_str());
140 if (ImGui::MenuItem(
"Remove from List")) {
151 float wrapPosX = ImGui::GetCursorPosX() + (cardWidth - 5.f);
152 ImGui::PushTextWrapPos(wrapPosX);
153 ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f),
"%s", m_projects[i].path.c_str());
154 ImGui::PopTextWrapPos();
156 ImGui::Text(
"Ver: %s", m_projects[i].version.c_str());
157 ImGui::Dummy(ImVec2(0.0f, 10.0f));
165 ImGui::PopStyleVar(2);
169 if (m_showAddExistingModal) ImGui::OpenPopup(
"Add Existing Project");
171 ImVec2 center = ImGui::GetMainViewport()->GetCenter();
172 ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
174 if (ImGui::BeginPopupModal(
"Add Existing Project", &m_showAddExistingModal, ImGuiWindowFlags_AlwaysAutoResize)) {
175 ImGui::Text(
"Enter absolute path to project folder:");
177 ImGui::InputTextWithHint(
"##pathInput",
"C:/Projects/MyProject", m_addExistingBuffer, IM_ARRAYSIZE(m_addExistingBuffer));
179 ImGui::InputTextWithHint(
"##pathInput",
"/home/user/Projects/MyProject", m_addExistingBuffer, IM_ARRAYSIZE(m_addExistingBuffer));
182 ImGui::Dummy(ImVec2(0, 10));
184 if (ImGui::Button(
"Add", ImVec2(100, 0))) {
186 m_showAddExistingModal =
false;
187 ImGui::CloseCurrentPopup();
190 if (ImGui::Button(
"Cancel", ImVec2(100, 0))) {
191 m_showAddExistingModal =
false;
192 ImGui::CloseCurrentPopup();
199 if (m_showMoveModal) ImGui::OpenPopup(
"Move Project");
201 ImVec2 center = ImGui::GetMainViewport()->GetCenter();
202 ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
204 if (ImGui::BeginPopupModal(
"Move Project", &m_showMoveModal, ImGuiWindowFlags_AlwaysAutoResize)) {
206 if (m_contextMenuTargetIndex >= 0 && m_contextMenuTargetIndex < m_projects.size()) {
207 const auto& proj = m_projects[m_contextMenuTargetIndex];
208 ImGui::Text(
"Moving project: '%s'", proj.name.c_str());
209 ImGui::TextDisabled(
"Current: %s", proj.path.c_str());
212 ImGui::Text(
"New Parent Directory:");
213 ImGui::InputText(
"##MoveDir", m_moveProjDestBuffer, IM_ARRAYSIZE(m_moveProjDestBuffer));
215 ImGui::Dummy(ImVec2(0, 15));
217 if (ImGui::Button(
"Move Folder", ImVec2(120, 0))) {
218 moveProject(m_contextMenuTargetIndex, m_moveProjDestBuffer);
219 m_showMoveModal =
false;
220 ImGui::CloseCurrentPopup();
223 m_showMoveModal =
false;
227 if (ImGui::Button(
"Cancel", ImVec2(120, 0))) {
228 m_showMoveModal =
false;
229 ImGui::CloseCurrentPopup();
236 if (index >= 0 && index < m_projects.size()) {
237 log(
"Removing project from list: %s", m_projects[index].name.c_str());
238 m_projects.erase(m_projects.begin() + index);
244 if (index < 0 || index >= m_projects.size())
return;
246 auto& proj = m_projects[index];
247 std::filesystem::path oldPath(proj.path);
248 std::filesystem::path newParent(newParentDir);
249 std::filesystem::path newPath = newParent / oldPath.filename();
251 if (!std::filesystem::exists(oldPath)) {
252 log(LogLevel::ERROR,
"Source path does not exist!");
255 if (std::filesystem::exists(newPath)) {
256 log(LogLevel::ERROR,
"Destination already exists: %s", newPath.string().c_str());
261 if (!std::filesystem::exists(newParent)) std::filesystem::create_directories(newParent);
263 std::filesystem::rename(oldPath, newPath);
264 log(
"Project moved to: %s", newPath.string().c_str());
266 proj.path = newPath.string();
270 }
catch (
const std::exception& e) {
271 log(LogLevel::ERROR,
"Failed to move project: %s", e.what());
276 if (!std::filesystem::exists(m_configPath))
return;
279 std::ifstream file(m_configPath);
283 for (
const auto& item : j) {
286 }
catch (
const std::exception& e) {
287 log(LogLevel::ERROR,
"Failed to load projects: %s", e.what());
292 nlohmann::json j = nlohmann::json::array();
293 for (
const auto& p : m_projects) {
297 {
"version", p.version}
300 std::ofstream file(m_configPath);
305 std::filesystem::path path(pathStr);
306 std::filesystem::path projectFile = path /
"VexProject.json";
308 if (std::filesystem::exists(projectFile)) {
310 std::ifstream f(projectFile);
314 std::string pName = j.value(
"project_name",
"Unknown Project");
315 std::string pVer = j.value(
"version",
"1");
317 for(
auto& p : m_projects) {
318 if(p.path == pathStr)
return;
321 m_projects.push_back({pName, pathStr, pVer});
323 log(
"Added project: %s", pName.c_str());
325 }
catch(
const std::exception& e) {
326 log(LogLevel::ERROR,
"Invalid VexProject.json at %s: %s", pathStr.c_str(), e.what());
329 log(LogLevel::ERROR,
"No VexProject.json found at %s", pathStr.c_str());
334 log(
"Project selected: %s", projectPath.c_str());
335 m_selectedProjectPath = projectPath;
340 if (m_showCreatorModal) {
341 ImGui::OpenPopup(
"Create New Project");
344 ImVec2 center = ImGui::GetMainViewport()->GetCenter();
345 ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
347 if (ImGui::BeginPopupModal(
"Create New Project", &m_showCreatorModal, ImGuiWindowFlags_AlwaysAutoResize)) {
349 ImGui::Text(
"Project Name:");
350 ImGui::InputText(
"##NewProjName", m_newProjName, IM_ARRAYSIZE(m_newProjName));
354 ImGui::Text(
"Location:");
355 ImGui::InputText(
"##NewProjDir", m_newProjParentDir, IM_ARRAYSIZE(m_newProjParentDir));
357 ImGui::TextDisabled(
"(?)");
358 if (ImGui::IsItemHovered())
359 ImGui::SetTooltip(
"The folder where your project folder will be created.\nDefault is Documents/VexProjects.");
361 ImGui::Dummy(ImVec2(0, 15));
363 ImGui::Dummy(ImVec2(0, 5));
365 if (ImGui::Button(
"Create", ImVec2(120, 0))) {
367 m_showCreatorModal =
false;
368 ImGui::CloseCurrentPopup();
371 if (ImGui::Button(
"Cancel", ImVec2(120, 0))) {
372 m_showCreatorModal =
false;
373 ImGui::CloseCurrentPopup();
381 std::string name(m_newProjName);
382 std::string parent(m_newProjParentDir);
384 if (name.empty() || parent.empty())
return;
387 std::filesystem::path templatePath = exeDir /
".." /
"Assets" /
"default" /
"NewProject";
389 if (!std::filesystem::exists(templatePath)) {
390 log(LogLevel::ERROR,
"Missing template: %s", templatePath.string().c_str());
394 std::filesystem::path destParent(parent);
395 std::filesystem::path newProjectPath = destParent / name;
397 if (std::filesystem::exists(newProjectPath)) {
398 log(LogLevel::ERROR,
"Project already exists at: %s", newProjectPath.string().c_str());
403 if (!std::filesystem::exists(destParent)) {
404 std::filesystem::create_directories(destParent);
407 std::filesystem::copy(templatePath, newProjectPath, std::filesystem::copy_options::recursive);
409 std::filesystem::path jsonPath = newProjectPath /
"VexProject.json";
410 if (std::filesystem::exists(jsonPath)) {
411 std::ifstream i(jsonPath);
415 j[
"project_name"] = name;
417 std::ofstream o(jsonPath);
422 log(
"Created project: %s", name.c_str());
424 }
catch (
const std::exception& e) {
425 log(LogLevel::ERROR,
"Creation failed: %s", e.what());
432 HRESULT result = SHGetFolderPathA(NULL, CSIDL_MYDOCUMENTS, NULL, SHGFP_TYPE_CURRENT, path);
433 if (SUCCEEDED(result)) {
434 return std::filesystem::path(path);
437 const char* homeDir = getenv(
"HOME");
439 return std::filesystem::path(homeDir) /
"Documents";
442 return std::filesystem::current_path();
This file defines PhysicsSystem class.
Specialized Engine class for the initial project selection screen.
void setInputMode(InputMode mode)
Function allowing for changing input mode at runtime.
InputMode getInputMode() const
Returns the current input mode.
void setResolutionMode(ResolutionMode mode)
Sets the resolution mode and updates the resolution manager.
ResolutionMode getResolutionMode() const
Returns the current resolution mode.
Engine(const char *title, int width, int height, GameInfo gInfo)
Constructor for the Engine class.
void drawAddExistingModal()
Draws the modal for adding an existing project.
void drawMoveProjectModal()
Draws the modal for moving an existing project.
void drawSelectorLayout(const SceneRenderData &data)
Internal helper to draw the ImGUI layout for the project selector.
void render() override
Overrides the base render function to draw the project selection UI.
std::filesystem::path getUserDocumentsDir()
Gets the path to the user's documents directory.
void removeProject(int index)
Removes a project from the list.
void saveKnownProjects()
Saves the current list of known projects to the configuration file.
void drawCreatorModal()
Draws the modal for creating a new project.
void createNewProject()
Creates a new project with the given name and path.
void loadKnownProjects()
Loads the list of recently known projects from the configuration file.
void moveProject(int index, const std::string &newParentDir)
Moves a project to a new parent directory.
void selectProject(const std::string &projectPath)
Sets the path of the project that the user selected and triggers the closure of the selector.
ProjectSelector(const char *title)
Constructs the ProjectSelector window.
void scanAndAddProject(const std::string &path)
Scans a directory path for a valid VEX project file and adds it to the list.
std::filesystem::path VEX_EXPORT GetExecutableDir()
Retrieves the directory containing the current executable.
@ NATIVE
Use window resolution.
void VEX_EXPORT log(const char *fmt,...)
Logs a formatted message.
this struct contains information about the game. like project name and version.
Data structure to pass state between render stages.