Improved File Manager + added Drag and drop support for scripts and Models for now lol.
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <cerrno>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cfloat>
|
#include <cfloat>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
@@ -16,6 +17,7 @@
|
|||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <shlobj.h>
|
#include <shlobj.h>
|
||||||
|
#include <shellapi.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace FileIcons {
|
namespace FileIcons {
|
||||||
@@ -301,6 +303,80 @@ namespace FileIcons {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
enum class CreateKind {
|
||||||
|
Folder,
|
||||||
|
CppScript,
|
||||||
|
Header,
|
||||||
|
Text,
|
||||||
|
Json,
|
||||||
|
Shader
|
||||||
|
};
|
||||||
|
|
||||||
|
fs::path makeUniquePath(const fs::path& basePath) {
|
||||||
|
if (!fs::exists(basePath)) {
|
||||||
|
return basePath;
|
||||||
|
}
|
||||||
|
std::string stem = basePath.stem().string();
|
||||||
|
std::string ext = basePath.extension().string();
|
||||||
|
for (int i = 1; i < 10000; ++i) {
|
||||||
|
fs::path candidate = basePath.parent_path() / (stem + std::to_string(i) + ext);
|
||||||
|
if (!fs::exists(candidate)) {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return basePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool writeFileContents(const fs::path& path, const std::string& contents) {
|
||||||
|
std::ofstream out(path, std::ios::binary);
|
||||||
|
if (!out) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out << contents;
|
||||||
|
return static_cast<bool>(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool openPathInShell(const fs::path& path) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
std::wstring widePath = path.wstring();
|
||||||
|
HINSTANCE result = ShellExecuteW(nullptr, L"open", widePath.c_str(), nullptr, nullptr, SW_SHOWNORMAL);
|
||||||
|
return reinterpret_cast<INT_PTR>(result) > 32;
|
||||||
|
#elif __linux__
|
||||||
|
std::string cmd = "xdg-open \"" + path.string() + "\"";
|
||||||
|
return std::system(cmd.c_str()) == 0;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void openPathInFileManager(const fs::path& path) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
std::wstring widePath = path.wstring();
|
||||||
|
if (fs::is_directory(path)) {
|
||||||
|
ShellExecuteW(nullptr, L"open", widePath.c_str(), nullptr, nullptr, SW_SHOWNORMAL);
|
||||||
|
} else {
|
||||||
|
std::wstring args = L"/select,\"" + widePath + L"\"";
|
||||||
|
ShellExecuteW(nullptr, L"open", L"explorer.exe", args.c_str(), nullptr, SW_SHOWNORMAL);
|
||||||
|
}
|
||||||
|
#elif __linux__
|
||||||
|
fs::path folder = fs::is_directory(path) ? path : path.parent_path();
|
||||||
|
std::string cmd = "xdg-open \"" + folder.string() + "\"";
|
||||||
|
std::system(cmd.c_str());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void openPathWithDialog(const fs::path& path) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
std::wstring widePath = path.wstring();
|
||||||
|
std::wstring args = L"shell32.dll,OpenAs_RunDLL \"" + widePath + L"\"";
|
||||||
|
ShellExecuteW(nullptr, nullptr, L"rundll32.exe", args.c_str(), nullptr, SW_SHOWNORMAL);
|
||||||
|
#else
|
||||||
|
openPathInShell(path);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void Engine::renderFileBrowserPanel() {
|
void Engine::renderFileBrowserPanel() {
|
||||||
ImGui::Begin("Project", &showFileBrowser);
|
ImGui::Begin("Project", &showFileBrowser);
|
||||||
@@ -314,6 +390,136 @@ void Engine::renderFileBrowserPanel() {
|
|||||||
fileBrowser.refresh();
|
fileBrowser.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool showDeletePopup = false;
|
||||||
|
static bool showRenamePopup = false;
|
||||||
|
static bool triggerDeletePopup = false;
|
||||||
|
static bool triggerRenamePopup = false;
|
||||||
|
static fs::path pendingDeletePath;
|
||||||
|
static fs::path pendingRenamePath;
|
||||||
|
static char renameName[256] = "";
|
||||||
|
|
||||||
|
auto openEntry = [&](const fs::directory_entry& entry) {
|
||||||
|
if (entry.is_directory()) {
|
||||||
|
fileBrowser.navigateTo(entry.path());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (fileBrowser.isModelFile(entry)) {
|
||||||
|
bool isObj = fileBrowser.isOBJFile(entry);
|
||||||
|
std::string defaultName = entry.path().stem().string();
|
||||||
|
if (isObj) {
|
||||||
|
pendingOBJPath = entry.path().string();
|
||||||
|
strncpy(importOBJName, defaultName.c_str(), sizeof(importOBJName) - 1);
|
||||||
|
importOBJName[sizeof(importOBJName) - 1] = '\0';
|
||||||
|
showImportOBJDialog = true;
|
||||||
|
} else {
|
||||||
|
pendingModelPath = entry.path().string();
|
||||||
|
strncpy(importModelName, defaultName.c_str(), sizeof(importModelName) - 1);
|
||||||
|
importModelName[sizeof(importModelName) - 1] = '\0';
|
||||||
|
showImportModelDialog = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (fileBrowser.getFileCategory(entry) == FileCategory::Material) {
|
||||||
|
if (SceneObject* sel = getSelectedObject()) {
|
||||||
|
sel->materialPath = entry.path().string();
|
||||||
|
loadMaterialFromFile(*sel);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (fileBrowser.isSceneFile(entry)) {
|
||||||
|
std::string sceneName = entry.path().stem().string();
|
||||||
|
loadScene(sceneName);
|
||||||
|
logToConsole("Loaded scene: " + sceneName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
openPathInShell(entry.path());
|
||||||
|
};
|
||||||
|
|
||||||
|
auto createEntry = [&](const fs::path& dir, CreateKind kind, const std::string& name) {
|
||||||
|
fs::path baseDir = dir.empty() ? fileBrowser.currentPath : dir;
|
||||||
|
fs::path target = baseDir / name;
|
||||||
|
if (kind != CreateKind::Folder && target.extension().empty()) {
|
||||||
|
switch (kind) {
|
||||||
|
case CreateKind::CppScript: target += ".cpp"; break;
|
||||||
|
case CreateKind::Header: target += ".h"; break;
|
||||||
|
case CreateKind::Text: target += ".txt"; break;
|
||||||
|
case CreateKind::Json: target += ".json"; break;
|
||||||
|
case CreateKind::Shader: target += ".glsl"; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
target = makeUniquePath(target);
|
||||||
|
|
||||||
|
std::error_code ec;
|
||||||
|
if (!baseDir.empty() && !fs::exists(baseDir)) {
|
||||||
|
fs::create_directories(baseDir, ec);
|
||||||
|
}
|
||||||
|
if (!baseDir.empty() && !fs::exists(baseDir)) {
|
||||||
|
addConsoleMessage("Create failed: target folder missing " + baseDir.string(),
|
||||||
|
ConsoleMessageType::Error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool created = false;
|
||||||
|
if (kind == CreateKind::Folder) {
|
||||||
|
created = fs::create_directories(target, ec);
|
||||||
|
} else {
|
||||||
|
std::string contents;
|
||||||
|
switch (kind) {
|
||||||
|
case CreateKind::CppScript:
|
||||||
|
contents =
|
||||||
|
"#include \"ScriptRuntime.h\"\n"
|
||||||
|
"#include \"SceneObject.h\"\n"
|
||||||
|
"#include \"ThirdParty/imgui/imgui.h\"\n"
|
||||||
|
"\n"
|
||||||
|
"extern \"C\" void Script_OnInspector(ScriptContext& ctx) {\n"
|
||||||
|
" ImGui::TextUnformatted(\"New Script\");\n"
|
||||||
|
"}\n"
|
||||||
|
"\n"
|
||||||
|
"void Begin(ScriptContext& ctx, float /*deltaTime*/) {\n"
|
||||||
|
"}\n"
|
||||||
|
"\n"
|
||||||
|
"void TickUpdate(ScriptContext& ctx, float /*deltaTime*/) {\n"
|
||||||
|
"}\n";
|
||||||
|
break;
|
||||||
|
case CreateKind::Header:
|
||||||
|
contents = "#pragma once\n";
|
||||||
|
break;
|
||||||
|
case CreateKind::Json:
|
||||||
|
contents = "{\n}\n";
|
||||||
|
break;
|
||||||
|
case CreateKind::Shader:
|
||||||
|
contents =
|
||||||
|
"// Shader entry point\n"
|
||||||
|
"void main() {\n"
|
||||||
|
"}\n";
|
||||||
|
break;
|
||||||
|
case CreateKind::Text:
|
||||||
|
default:
|
||||||
|
contents.clear();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
created = writeFileContents(target, contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (created) {
|
||||||
|
fileBrowser.selectedFile = target;
|
||||||
|
fileBrowser.refresh();
|
||||||
|
addConsoleMessage("Created: " + target.string(), ConsoleMessageType::Success);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string reason = ec ? ec.message() : std::strerror(errno);
|
||||||
|
if (kind == CreateKind::Folder) {
|
||||||
|
addConsoleMessage("Create failed: unable to create folder " + target.string() + " (" + reason + ")",
|
||||||
|
ConsoleMessageType::Error);
|
||||||
|
} else {
|
||||||
|
addConsoleMessage("Create failed: unable to write " + target.string() + " (" + reason + ")",
|
||||||
|
ConsoleMessageType::Error);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
// Get colors for categories
|
// Get colors for categories
|
||||||
auto getCategoryColor = [](FileCategory cat) -> ImU32 {
|
auto getCategoryColor = [](FileCategory cat) -> ImU32 {
|
||||||
switch (cat) {
|
switch (cat) {
|
||||||
@@ -555,41 +761,50 @@ void Engine::renderFileBrowserPanel() {
|
|||||||
|
|
||||||
// Handle double click
|
// Handle double click
|
||||||
if (doubleClicked) {
|
if (doubleClicked) {
|
||||||
if (entry.is_directory()) {
|
openEntry(entry);
|
||||||
fileBrowser.navigateTo(entry.path());
|
|
||||||
} else if (fileBrowser.isModelFile(entry)) {
|
|
||||||
bool isObj = fileBrowser.isOBJFile(entry);
|
|
||||||
std::string defaultName = entry.path().stem().string();
|
|
||||||
if (isObj) {
|
|
||||||
pendingOBJPath = entry.path().string();
|
|
||||||
strncpy(importOBJName, defaultName.c_str(), sizeof(importOBJName) - 1);
|
|
||||||
showImportOBJDialog = true;
|
|
||||||
} else {
|
|
||||||
pendingModelPath = entry.path().string();
|
|
||||||
strncpy(importModelName, defaultName.c_str(), sizeof(importModelName) - 1);
|
|
||||||
showImportModelDialog = true;
|
|
||||||
}
|
|
||||||
} else if (fileBrowser.getFileCategory(entry) == FileCategory::Material) {
|
|
||||||
if (SceneObject* sel = getSelectedObject()) {
|
|
||||||
sel->materialPath = entry.path().string();
|
|
||||||
loadMaterialFromFile(*sel);
|
|
||||||
}
|
|
||||||
} else if (fileBrowser.isSceneFile(entry)) {
|
|
||||||
std::string sceneName = entry.path().stem().string();
|
|
||||||
loadScene(sceneName);
|
|
||||||
logToConsole("Loaded scene: " + sceneName);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Context menu
|
// Context menu
|
||||||
if (ImGui::BeginPopupContextItem("FileContextMenu")) {
|
if (ImGui::BeginPopupContextItem("FileContextMenu")) {
|
||||||
if (ImGui::MenuItem("Open")) {
|
if (ImGui::MenuItem("Open")) {
|
||||||
if (entry.is_directory()) {
|
openEntry(entry);
|
||||||
fileBrowser.navigateTo(entry.path());
|
|
||||||
} else if (fileBrowser.isSceneFile(entry)) {
|
|
||||||
std::string sceneName = entry.path().stem().string();
|
|
||||||
loadScene(sceneName);
|
|
||||||
}
|
}
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (!entry.is_directory() && ImGui::MenuItem("Open With...")) {
|
||||||
|
openPathWithDialog(entry.path());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (entry.is_directory() && ImGui::BeginMenu("New")) {
|
||||||
|
if (ImGui::MenuItem("Folder")) {
|
||||||
|
createEntry(entry.path(), CreateKind::Folder, "New Folder");
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("C++ Script")) {
|
||||||
|
createEntry(entry.path(), CreateKind::CppScript, "NewScript.cpp");
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("Header")) {
|
||||||
|
createEntry(entry.path(), CreateKind::Header, "NewHeader.h");
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("Text File")) {
|
||||||
|
createEntry(entry.path(), CreateKind::Text, "NewFile.txt");
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("JSON File")) {
|
||||||
|
createEntry(entry.path(), CreateKind::Json, "NewData.json");
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("Shader")) {
|
||||||
|
createEntry(entry.path(), CreateKind::Shader, "NewShader.glsl");
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("Material")) {
|
||||||
|
fs::path target = entry.path() / "NewMaterial.mat";
|
||||||
|
int counter = 1;
|
||||||
|
while (fs::exists(target)) {
|
||||||
|
target = entry.path() / ("NewMaterial" + std::to_string(counter++) + ".mat");
|
||||||
|
}
|
||||||
|
SceneObject temp("Material", ObjectType::Cube, -1);
|
||||||
|
temp.materialPath = target.string();
|
||||||
|
saveMaterialToFile(temp);
|
||||||
|
fileBrowser.needsRefresh = true;
|
||||||
|
}
|
||||||
|
ImGui::EndMenu();
|
||||||
}
|
}
|
||||||
if (fileBrowser.isModelFile(entry)) {
|
if (fileBrowser.isModelFile(entry)) {
|
||||||
bool isObj = fileBrowser.isOBJFile(entry);
|
bool isObj = fileBrowser.isOBJFile(entry);
|
||||||
@@ -630,18 +845,34 @@ void Engine::renderFileBrowserPanel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
if (ImGui::MenuItem("Show in Explorer")) {
|
if (ImGui::MenuItem("Open in File Explorer")) {
|
||||||
#ifdef _WIN32
|
openPathInFileManager(entry.path());
|
||||||
std::string cmd = "explorer \"" + entry.path().parent_path().string() + "\"";
|
}
|
||||||
system(cmd.c_str());
|
if (ImGui::MenuItem("Rename")) {
|
||||||
#elif __linux__
|
pendingRenamePath = entry.path();
|
||||||
std::string cmd = "xdg-open \"" + entry.path().parent_path().string() + "\"";
|
std::string baseName = pendingRenamePath.filename().string();
|
||||||
system(cmd.c_str());
|
std::strncpy(renameName, baseName.c_str(), sizeof(renameName) - 1);
|
||||||
#endif
|
renameName[sizeof(renameName) - 1] = '\0';
|
||||||
|
showRenamePopup = true;
|
||||||
|
triggerRenamePopup = true;
|
||||||
|
addConsoleMessage("Rename request: " + pendingRenamePath.string(), ConsoleMessageType::Info);
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem(entry.is_directory() ? "Delete Folder" : "Delete File")) {
|
||||||
|
pendingDeletePath = entry.path();
|
||||||
|
showDeletePopup = true;
|
||||||
|
triggerDeletePopup = true;
|
||||||
|
addConsoleMessage("Delete request: " + pendingDeletePath.string(), ConsoleMessageType::Info);
|
||||||
}
|
}
|
||||||
ImGui::EndPopup();
|
ImGui::EndPopup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!entry.is_directory() && ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceAllowNullID)) {
|
||||||
|
std::string payloadPath = entry.path().string();
|
||||||
|
ImGui::SetDragDropPayload("FILE_PATH", payloadPath.c_str(), payloadPath.size() + 1);
|
||||||
|
ImGui::Text("%s", filename.c_str());
|
||||||
|
ImGui::EndDragDropSource();
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::PopID();
|
ImGui::PopID();
|
||||||
}
|
}
|
||||||
ImGui::EndTable();
|
ImGui::EndTable();
|
||||||
@@ -664,42 +895,51 @@ void Engine::renderFileBrowserPanel() {
|
|||||||
fileBrowser.selectedFile = entry.path();
|
fileBrowser.selectedFile = entry.path();
|
||||||
|
|
||||||
if (ImGui::IsMouseDoubleClicked(0)) {
|
if (ImGui::IsMouseDoubleClicked(0)) {
|
||||||
if (entry.is_directory()) {
|
openEntry(entry);
|
||||||
fileBrowser.navigateTo(entry.path());
|
|
||||||
} else if (fileBrowser.isModelFile(entry)) {
|
|
||||||
bool isObj = fileBrowser.isOBJFile(entry);
|
|
||||||
std::string defaultName = entry.path().stem().string();
|
|
||||||
if (isObj) {
|
|
||||||
pendingOBJPath = entry.path().string();
|
|
||||||
strncpy(importOBJName, defaultName.c_str(), sizeof(importOBJName) - 1);
|
|
||||||
showImportOBJDialog = true;
|
|
||||||
} else {
|
|
||||||
pendingModelPath = entry.path().string();
|
|
||||||
strncpy(importModelName, defaultName.c_str(), sizeof(importModelName) - 1);
|
|
||||||
showImportModelDialog = true;
|
|
||||||
}
|
|
||||||
} else if (fileBrowser.getFileCategory(entry) == FileCategory::Material) {
|
|
||||||
if (SceneObject* sel = getSelectedObject()) {
|
|
||||||
sel->materialPath = entry.path().string();
|
|
||||||
loadMaterialFromFile(*sel);
|
|
||||||
}
|
|
||||||
} else if (fileBrowser.isSceneFile(entry)) {
|
|
||||||
std::string sceneName = entry.path().stem().string();
|
|
||||||
loadScene(sceneName);
|
|
||||||
logToConsole("Loaded scene: " + sceneName);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Context menu
|
// Context menu
|
||||||
if (ImGui::BeginPopupContextItem("FileContextMenu")) {
|
if (ImGui::BeginPopupContextItem("FileContextMenu")) {
|
||||||
if (ImGui::MenuItem("Open")) {
|
if (ImGui::MenuItem("Open")) {
|
||||||
if (entry.is_directory()) {
|
openEntry(entry);
|
||||||
fileBrowser.navigateTo(entry.path());
|
|
||||||
} else if (fileBrowser.isSceneFile(entry)) {
|
|
||||||
std::string sceneName = entry.path().stem().string();
|
|
||||||
loadScene(sceneName);
|
|
||||||
}
|
}
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (!entry.is_directory() && ImGui::MenuItem("Open With...")) {
|
||||||
|
openPathWithDialog(entry.path());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (entry.is_directory() && ImGui::BeginMenu("New")) {
|
||||||
|
if (ImGui::MenuItem("Folder")) {
|
||||||
|
createEntry(entry.path(), CreateKind::Folder, "New Folder");
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("C++ Script")) {
|
||||||
|
createEntry(entry.path(), CreateKind::CppScript, "NewScript.cpp");
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("Header")) {
|
||||||
|
createEntry(entry.path(), CreateKind::Header, "NewHeader.h");
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("Text File")) {
|
||||||
|
createEntry(entry.path(), CreateKind::Text, "NewFile.txt");
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("JSON File")) {
|
||||||
|
createEntry(entry.path(), CreateKind::Json, "NewData.json");
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("Shader")) {
|
||||||
|
createEntry(entry.path(), CreateKind::Shader, "NewShader.glsl");
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("Material")) {
|
||||||
|
fs::path target = entry.path() / "NewMaterial.mat";
|
||||||
|
int counter = 1;
|
||||||
|
while (fs::exists(target)) {
|
||||||
|
target = entry.path() / ("NewMaterial" + std::to_string(counter++) + ".mat");
|
||||||
|
}
|
||||||
|
SceneObject temp("Material", ObjectType::Cube, -1);
|
||||||
|
temp.materialPath = target.string();
|
||||||
|
saveMaterialToFile(temp);
|
||||||
|
fileBrowser.needsRefresh = true;
|
||||||
|
}
|
||||||
|
ImGui::EndMenu();
|
||||||
}
|
}
|
||||||
if (fileBrowser.isModelFile(entry)) {
|
if (fileBrowser.isModelFile(entry)) {
|
||||||
bool isObj = fileBrowser.isOBJFile(entry);
|
bool isObj = fileBrowser.isOBJFile(entry);
|
||||||
@@ -743,18 +983,34 @@ void Engine::renderFileBrowserPanel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
if (ImGui::MenuItem("Show in Explorer")) {
|
if (ImGui::MenuItem("Open in File Explorer")) {
|
||||||
#ifdef _WIN32
|
openPathInFileManager(entry.path());
|
||||||
std::string cmd = "explorer \"" + entry.path().parent_path().string() + "\"";
|
}
|
||||||
system(cmd.c_str());
|
if (ImGui::MenuItem("Rename")) {
|
||||||
#elif __linux__
|
pendingRenamePath = entry.path();
|
||||||
std::string cmd = "xdg-open \"" + entry.path().parent_path().string() + "\"";
|
std::string baseName = pendingRenamePath.filename().string();
|
||||||
system(cmd.c_str());
|
std::strncpy(renameName, baseName.c_str(), sizeof(renameName) - 1);
|
||||||
#endif
|
renameName[sizeof(renameName) - 1] = '\0';
|
||||||
|
showRenamePopup = true;
|
||||||
|
triggerRenamePopup = true;
|
||||||
|
addConsoleMessage("Rename request: " + pendingRenamePath.string(), ConsoleMessageType::Info);
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem(entry.is_directory() ? "Delete Folder" : "Delete File")) {
|
||||||
|
pendingDeletePath = entry.path();
|
||||||
|
showDeletePopup = true;
|
||||||
|
triggerDeletePopup = true;
|
||||||
|
addConsoleMessage("Delete request: " + pendingDeletePath.string(), ConsoleMessageType::Info);
|
||||||
}
|
}
|
||||||
ImGui::EndPopup();
|
ImGui::EndPopup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!entry.is_directory() && ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceAllowNullID)) {
|
||||||
|
std::string payloadPath = entry.path().string();
|
||||||
|
ImGui::SetDragDropPayload("FILE_PATH", payloadPath.c_str(), payloadPath.size() + 1);
|
||||||
|
ImGui::Text("%s", filename.c_str());
|
||||||
|
ImGui::EndDragDropSource();
|
||||||
|
}
|
||||||
|
|
||||||
// Draw icon inline
|
// Draw icon inline
|
||||||
ImGui::SameLine(4);
|
ImGui::SameLine(4);
|
||||||
ImVec2 iconPos = ImGui::GetCursorScreenPos();
|
ImVec2 iconPos = ImGui::GetCursorScreenPos();
|
||||||
@@ -781,8 +1037,125 @@ void Engine::renderFileBrowserPanel() {
|
|||||||
ImGui::PopStyleVar();
|
ImGui::PopStyleVar();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::EndChild();
|
if (ImGui::BeginPopupContextWindow("FileBrowserEmptyContext", ImGuiPopupFlags_NoOpenOverItems | ImGuiPopupFlags_MouseButtonRight)) {
|
||||||
ImGui::PopStyleColor();
|
if (ImGui::MenuItem("Open in File Explorer")) {
|
||||||
ImGui::End();
|
openPathInFileManager(fileBrowser.currentPath);
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("Refresh")) {
|
||||||
|
fileBrowser.needsRefresh = true;
|
||||||
|
}
|
||||||
|
ImGui::Separator();
|
||||||
|
if (ImGui::BeginMenu("New")) {
|
||||||
|
if (ImGui::MenuItem("Folder")) {
|
||||||
|
createEntry(fileBrowser.currentPath, CreateKind::Folder, "New Folder");
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("C++ Script")) {
|
||||||
|
createEntry(fileBrowser.currentPath, CreateKind::CppScript, "NewScript.cpp");
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("Header")) {
|
||||||
|
createEntry(fileBrowser.currentPath, CreateKind::Header, "NewHeader.h");
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("Text File")) {
|
||||||
|
createEntry(fileBrowser.currentPath, CreateKind::Text, "NewFile.txt");
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("JSON File")) {
|
||||||
|
createEntry(fileBrowser.currentPath, CreateKind::Json, "NewData.json");
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("Shader")) {
|
||||||
|
createEntry(fileBrowser.currentPath, CreateKind::Shader, "NewShader.glsl");
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("Material")) {
|
||||||
|
fs::path target = fileBrowser.currentPath / "NewMaterial.mat";
|
||||||
|
int counter = 1;
|
||||||
|
while (fs::exists(target)) {
|
||||||
|
target = fileBrowser.currentPath / ("NewMaterial" + std::to_string(counter++) + ".mat");
|
||||||
|
}
|
||||||
|
SceneObject temp("Material", ObjectType::Cube, -1);
|
||||||
|
temp.materialPath = target.string();
|
||||||
|
saveMaterialToFile(temp);
|
||||||
|
fileBrowser.needsRefresh = true;
|
||||||
|
}
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
ImGui::EndPopup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGui::EndChild();
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
|
||||||
|
if (triggerDeletePopup) {
|
||||||
|
ImGui::OpenPopup("Confirm Delete");
|
||||||
|
triggerDeletePopup = false;
|
||||||
|
}
|
||||||
|
if (ImGui::BeginPopupModal("Confirm Delete", &showDeletePopup, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||||
|
ImGui::Text("Delete %s?", pendingDeletePath.filename().string().c_str());
|
||||||
|
ImGui::TextDisabled("%s", pendingDeletePath.string().c_str());
|
||||||
|
ImGui::Spacing();
|
||||||
|
if (ImGui::Button("Delete", ImVec2(120, 0))) {
|
||||||
|
std::error_code ec;
|
||||||
|
fs::remove_all(pendingDeletePath, ec);
|
||||||
|
if (fileBrowser.selectedFile == pendingDeletePath) {
|
||||||
|
fileBrowser.selectedFile.clear();
|
||||||
|
}
|
||||||
|
fileBrowser.needsRefresh = true;
|
||||||
|
if (ec) {
|
||||||
|
addConsoleMessage("Delete failed: " + pendingDeletePath.string() + " (" + ec.message() + ")",
|
||||||
|
ConsoleMessageType::Error);
|
||||||
|
} else {
|
||||||
|
addConsoleMessage("Deleted: " + pendingDeletePath.string(), ConsoleMessageType::Success);
|
||||||
|
}
|
||||||
|
showDeletePopup = false;
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Cancel", ImVec2(120, 0))) {
|
||||||
|
showDeletePopup = false;
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (triggerRenamePopup) {
|
||||||
|
ImGui::OpenPopup("Rename Item");
|
||||||
|
triggerRenamePopup = false;
|
||||||
|
}
|
||||||
|
if (ImGui::BeginPopupModal("Rename Item", &showRenamePopup, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||||
|
ImGui::Text("Rename %s", pendingRenamePath.filename().string().c_str());
|
||||||
|
ImGui::Spacing();
|
||||||
|
ImGui::InputText("New Name", renameName, sizeof(renameName));
|
||||||
|
bool canRename = std::strlen(renameName) > 0;
|
||||||
|
ImGui::BeginDisabled(!canRename);
|
||||||
|
if (ImGui::Button("Rename", ImVec2(120, 0))) {
|
||||||
|
fs::path newPath = pendingRenamePath.parent_path() / renameName;
|
||||||
|
if (newPath == pendingRenamePath) {
|
||||||
|
addConsoleMessage("Rename skipped: name unchanged", ConsoleMessageType::Info);
|
||||||
|
} else if (fs::exists(newPath)) {
|
||||||
|
addConsoleMessage("Rename failed: target exists " + newPath.string(), ConsoleMessageType::Error);
|
||||||
|
} else {
|
||||||
|
std::error_code ec;
|
||||||
|
fs::rename(pendingRenamePath, newPath, ec);
|
||||||
|
if (ec) {
|
||||||
|
addConsoleMessage("Rename failed: " + pendingRenamePath.string() + " (" + ec.message() + ")",
|
||||||
|
ConsoleMessageType::Error);
|
||||||
|
} else {
|
||||||
|
if (fileBrowser.selectedFile == pendingRenamePath) {
|
||||||
|
fileBrowser.selectedFile = newPath;
|
||||||
|
}
|
||||||
|
fileBrowser.needsRefresh = true;
|
||||||
|
addConsoleMessage("Renamed to: " + newPath.string(), ConsoleMessageType::Success);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
showRenamePopup = false;
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Cancel", ImVec2(120, 0))) {
|
||||||
|
showRenamePopup = false;
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
|||||||
@@ -58,11 +58,36 @@ void Engine::renderHierarchyPanel() {
|
|||||||
std::string filter = searchBuffer;
|
std::string filter = searchBuffer;
|
||||||
std::transform(filter.begin(), filter.end(), filter.begin(), ::tolower);
|
std::transform(filter.begin(), filter.end(), filter.begin(), ::tolower);
|
||||||
|
|
||||||
|
auto importDroppedModel = [&](const fs::path& path, int parentId) {
|
||||||
|
std::error_code ec;
|
||||||
|
fs::directory_entry entry(path, ec);
|
||||||
|
if (ec) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!fileBrowser.isModelFile(entry)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
size_t beforeCount = sceneObjects.size();
|
||||||
|
if (fileBrowser.isOBJFile(entry)) {
|
||||||
|
importOBJToScene(path.string(), "");
|
||||||
|
} else {
|
||||||
|
importModelToScene(path.string(), "");
|
||||||
|
}
|
||||||
|
if (sceneObjects.size() > beforeCount && parentId >= 0) {
|
||||||
|
int newId = sceneObjects.back().id;
|
||||||
|
setParent(newId, parentId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (ImGui::BeginDragDropTarget()) {
|
if (ImGui::BeginDragDropTarget()) {
|
||||||
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("SCENE_OBJECT")) {
|
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("SCENE_OBJECT")) {
|
||||||
int draggedId = *(const int*)payload->Data;
|
int draggedId = *(const int*)payload->Data;
|
||||||
setParent(draggedId, -1);
|
setParent(draggedId, -1);
|
||||||
}
|
}
|
||||||
|
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("FILE_PATH")) {
|
||||||
|
const char* path = static_cast<const char*>(payload->Data);
|
||||||
|
importDroppedModel(fs::path(path), -1);
|
||||||
|
}
|
||||||
ImGui::EndDragDropTarget();
|
ImGui::EndDragDropTarget();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,6 +193,24 @@ void Engine::renderObjectNode(SceneObject& obj, const std::string& filter) {
|
|||||||
ImGui::EndDragDropSource();
|
ImGui::EndDragDropSource();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto importDroppedModel = [&](const fs::path& path, int parentId) {
|
||||||
|
std::error_code ec;
|
||||||
|
fs::directory_entry entry(path, ec);
|
||||||
|
if (ec || !fileBrowser.isModelFile(entry)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
size_t beforeCount = sceneObjects.size();
|
||||||
|
if (fileBrowser.isOBJFile(entry)) {
|
||||||
|
importOBJToScene(path.string(), "");
|
||||||
|
} else {
|
||||||
|
importModelToScene(path.string(), "");
|
||||||
|
}
|
||||||
|
if (sceneObjects.size() > beforeCount && parentId >= 0) {
|
||||||
|
int newId = sceneObjects.back().id;
|
||||||
|
setParent(newId, parentId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (ImGui::BeginDragDropTarget()) {
|
if (ImGui::BeginDragDropTarget()) {
|
||||||
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("SCENE_OBJECT")) {
|
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("SCENE_OBJECT")) {
|
||||||
int draggedId = *(const int*)payload->Data;
|
int draggedId = *(const int*)payload->Data;
|
||||||
@@ -175,6 +218,26 @@ void Engine::renderObjectNode(SceneObject& obj, const std::string& filter) {
|
|||||||
setParent(draggedId, obj.id);
|
setParent(draggedId, obj.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("FILE_PATH")) {
|
||||||
|
const char* path = static_cast<const char*>(payload->Data);
|
||||||
|
std::error_code ec;
|
||||||
|
fs::directory_entry entry(path, ec);
|
||||||
|
if (!ec) {
|
||||||
|
if (fileBrowser.isModelFile(entry)) {
|
||||||
|
importDroppedModel(fs::path(path), obj.id);
|
||||||
|
} else if (fileBrowser.getFileCategory(entry) == FileCategory::Script) {
|
||||||
|
auto alreadyAssigned = std::any_of(obj.scripts.begin(), obj.scripts.end(),
|
||||||
|
[&](const ScriptComponent& sc) { return sc.path == path; });
|
||||||
|
if (!alreadyAssigned) {
|
||||||
|
ScriptComponent sc;
|
||||||
|
sc.path = path;
|
||||||
|
obj.scripts.push_back(sc);
|
||||||
|
projectManager.currentProject.hasUnsavedChanges = true;
|
||||||
|
addConsoleMessage("Assigned script to " + obj.name, ConsoleMessageType::Success);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
ImGui::EndDragDropTarget();
|
ImGui::EndDragDropTarget();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -621,6 +621,27 @@ void Engine::renderViewport() {
|
|||||||
mouseOverViewportImage = ImGui::IsItemHovered();
|
mouseOverViewportImage = ImGui::IsItemHovered();
|
||||||
ImDrawList* viewportDrawList = ImGui::GetWindowDrawList();
|
ImDrawList* viewportDrawList = ImGui::GetWindowDrawList();
|
||||||
|
|
||||||
|
auto importDroppedModel = [&](const fs::path& path) {
|
||||||
|
std::error_code ec;
|
||||||
|
fs::directory_entry entry(path, ec);
|
||||||
|
if (ec || !fileBrowser.isModelFile(entry)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (fileBrowser.isOBJFile(entry)) {
|
||||||
|
importOBJToScene(path.string(), "");
|
||||||
|
} else {
|
||||||
|
importModelToScene(path.string(), "");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (ImGui::BeginDragDropTarget()) {
|
||||||
|
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("FILE_PATH")) {
|
||||||
|
const char* path = static_cast<const char*>(payload->Data);
|
||||||
|
importDroppedModel(fs::path(path));
|
||||||
|
}
|
||||||
|
ImGui::EndDragDropTarget();
|
||||||
|
}
|
||||||
|
|
||||||
auto setCameraFacing = [&](const glm::vec3& dir) {
|
auto setCameraFacing = [&](const glm::vec3& dir) {
|
||||||
glm::vec3 worldUp = glm::vec3(0, 1, 0);
|
glm::vec3 worldUp = glm::vec3(0, 1, 0);
|
||||||
glm::vec3 n = glm::normalize(dir);
|
glm::vec3 n = glm::normalize(dir);
|
||||||
|
|||||||
Reference in New Issue
Block a user