added first selectable, and modyfiable assets to system, Mesh, Material, and Texture. Texture ist WIP, because no subassets are created jet, well technicaly everything is WIP bit nevermind XD

This commit is contained in:
2026-05-16 23:43:52 +02:00
parent 95bdb1c8bb
commit 5e7c530e99
24 changed files with 1837 additions and 157 deletions

View File

@@ -20,6 +20,7 @@ TSE::EDITOR::EditorSubsystem::EditorSubsystem(ConsolView* cvp) : sv(nullptr), ed
controller.AddGuiElement("Properties", &pv);
controller.AddGuiElement("Debug", &dv);
controller.AddGuiElement("Camera", &camv);
controller.AddGuiElement("Assets", &av);
BehaviourScriptRegistry::RegisterBehaviourScript("Image", []() -> BehaviourScript* {return new Image();});
BehaviourScriptRegistry::RegisterBehaviourScript("Image Animation", []() -> BehaviourScript* {return new ImageAnimation();});

View File

@@ -7,6 +7,7 @@
#include "UI/windows/HirearchieView.hpp"
#include "UI/windows/PropertiesView.hpp"
#include "UI/windows/SceneView.hpp"
#include "UI/windows/AssetsView.hpp"
#include "interfaces/IRenderTexture.hpp"
namespace TSE::EDITOR
@@ -22,6 +23,7 @@ namespace TSE::EDITOR
CameraView camv;
TSE::IRenderTexture* rt;
SceneView sv;
AssetsView av;
Layer editorLayer;
EditorSubsystem(ConsolView* cvp);

View File

@@ -1,12 +1,167 @@
#include "ElementDrawer.hpp"
#include "BehaviourScriptRegistry.hpp"
#include "elements/ShaderRegistry.hpp"
#include "elements/AssetLibrary.hpp"
#include "BehaviourScripts/Camera.hpp"
#include "windows/HirearchieView.hpp"
#include "windows/PropertiesView.hpp"
#include <algorithm>
#include <any>
#include <cctype>
namespace TSE::EDITOR
{
namespace
{
char assetPickerSearchBuffer[128] = "";
std::string ToLowerCopy(std::string value)
{
std::transform(value.begin(), value.end(), value.begin(), [](unsigned char c)
{
return static_cast<char>(std::tolower(c));
});
return value;
}
template<typename T>
std::vector<T*> GetAssetsOfType()
{
std::vector<T*> result;
int count = AssetLibrary::GetValueCount();
for(int i = 0; i < count; i++)
{
auto [ptr, type, id] = AssetLibrary::GetValueAt(i);
if(type != typeid(T).name() && type != typeid(T*).name())
{
continue;
}
try
{
result.push_back(std::any_cast<T*>(ptr));
}
catch(const std::bad_any_cast&)
{
}
}
return result;
}
template<typename T, typename NameGetter>
T* DrawAssetPickerPopup(const char* popupId, const char* searchHint, const char* emptyText, NameGetter getName)
{
T* selectedAsset = nullptr;
ImGui::SetNextWindowSize({360.0f, 420.0f}, ImGuiCond_Appearing);
if(ImGui::BeginPopup(popupId))
{
ImGui::PushItemWidth(-1.0f);
ImGui::InputTextWithHint("##AssetPickerSearch", searchHint, assetPickerSearchBuffer, IM_ARRAYSIZE(assetPickerSearchBuffer));
ImGui::PopItemWidth();
ImGui::Separator();
std::string filterText = ToLowerCopy(assetPickerSearchBuffer);
std::vector<T*> assets = GetAssetsOfType<T>();
if(ImGui::BeginChild("##AssetPickerList", {0.0f, 0.0f}, ImGuiChildFlags_None, ImGuiWindowFlags_AlwaysVerticalScrollbar))
{
int shownCount = 0;
for(T* asset : assets)
{
if(asset == nullptr)
{
continue;
}
std::string assetName = getName(asset);
if(!filterText.empty() && ToLowerCopy(assetName).find(filterText) == std::string::npos)
{
continue;
}
ImGui::PushID(asset);
if(ImGui::Selectable(assetName.c_str()))
{
selectedAsset = asset;
ImGui::CloseCurrentPopup();
}
ImGui::PopID();
shownCount++;
}
if(shownCount == 0)
{
ImGui::TextDisabled("%s", emptyText);
}
}
ImGui::EndChild();
ImGui::EndPopup();
}
return selectedAsset;
}
template<typename T>
bool BeginAssetField(T** element, const char* popupId)
{
if(element == nullptr)
{
ImGui::TextDisabled("No Asset Field");
return false;
}
ImGui::PushID(element);
float buttonWidth = ImGui::GetFrameHeight();
float fieldHeight = std::max(36.0f, ImGui::GetFrameHeightWithSpacing() + ImGui::GetStyle().WindowPadding.y * 2.0f);
ImGui::BeginChild("##AssetField", {0.0f, fieldHeight}, ImGuiChildFlags_Borders, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
if(ImGui::Button("+##SelectAsset", {buttonWidth, 0.0f}))
{
assetPickerSearchBuffer[0] = '\0';
ImGui::OpenPopup(popupId);
}
ImGui::SameLine();
ImGui::BeginChild("##AssetFieldValue", {0.0f, 0.0f}, ImGuiChildFlags_None, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
return true;
}
template<typename T>
void EndAssetField()
{
ImGui::EndChild();
}
template<typename T>
void OpenAssetPropertiesOnDoubleClick(T* asset)
{
if(asset == nullptr)
{
return;
}
if(ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left))
{
PropertiesView::SetInspectorElement(InspectableType::IAsset, static_cast<IAsset*>(asset));
}
}
template<typename T, typename NameGetter>
void AssetSelector(T** element, const char* popupId, const char* searchHint, const char* emptyText, NameGetter getName)
{
T* selectedAsset = DrawAssetPickerPopup<T>(popupId, searchHint, emptyText, getName);
if(selectedAsset != nullptr)
{
*element = selectedAsset;
}
ImGui::EndChild();
ImGui::PopID();
}
}
#pragma region helper
bool InputText(const char* label, std::string& str, size_t bufferSize = 256) {
std::vector<char> buffer(bufferSize);
@@ -222,15 +377,7 @@ namespace TSE::EDITOR
void ElementDrawer::Draw(Renderable *element, const bool &debug)
{
ImGui::SeparatorText("Material");
int height = 100;
if(element->GetMaterial() == nullptr)
{
height = 35;
}
ImVec2 size(0, height);
ImGui::BeginChild("MaterialViewer", size, ImGuiChildFlags_Borders);
Draw(element->GetMaterial(), debug);
ImGui::EndChild();
DrawMaterialField(element->GetMaterialRef());
}
void ElementDrawer::Draw(MeshContainer *element, const bool &debug)
{
@@ -240,7 +387,7 @@ namespace TSE::EDITOR
height = 35;
}
ImVec2 size(0, height);
Draw(element->GetMesh(), debug, "Mesh", true);
DrawMeshField(element->GetMeshRef());
}
void ElementDrawer::Draw(Image *element, const bool &debug)
{
@@ -444,7 +591,7 @@ namespace TSE::EDITOR
Texture* value = element->GetValue<Texture*>(name);
Draw(value, debug, name , true);
}
if (type == typeid(uint).name())
else if (type == typeid(uint).name())
{
int value = element->GetValue<uint>(name);
if(ImGui::InputInt(name.c_str(), &value))
@@ -481,11 +628,6 @@ namespace TSE::EDITOR
}
void ElementDrawer::Draw(Mesh *element, const bool &debug, const std::string &label, const bool small)
{
if(element == nullptr)
{
ImGui::Text("No Mesh Set");
return;
}
if(small) DrawMeshCompact(element, debug, label);
else DrawMeshNormal(element, debug, label);
}
@@ -943,6 +1085,24 @@ namespace TSE::EDITOR
ImGui::TextDisabled(("Chunk Count: " + std::to_string(element->GetChunkCount())).c_str());
}
}
void ElementDrawer::Draw(IAsset *element, const bool &debug)
{
if (element->assetType() == "material")
{
Draw((Material*)element, debug);
}
else if (element->assetType() == "texture")
{
Draw((Texture*)element, debug);
}
else if (element->assetType() == "mesh")
{
Draw((Mesh*)element, debug);
}
element->SaveAsset();
}
void ElementDrawer::DrawAudioClipCompact(AudioClip *element, const bool &debug, const std::string &label)
{
float item_spacing = ImGui::GetStyle().ItemSpacing.x;
@@ -1034,6 +1194,21 @@ namespace TSE::EDITOR
}
void ElementDrawer::DrawMeshCompact(Mesh *element, const bool &debug, const std::string &label)
{
if(element == nullptr)
{
ImGui::Text("No Mesh Set");
return;
}
ImGui::Text(element->name.c_str());
}
void ElementDrawer::DrawMeshNormal(Mesh *element, const bool &debug, const std::string &label)
{
if(element == nullptr)
{
ImGui::Text("No Mesh Set");
return;
}
float item_spacing = ImGui::GetStyle().ItemSpacing.x;
ImVec2 label_size = ImGui::CalcTextSize(label.c_str());
@@ -1060,54 +1235,6 @@ namespace TSE::EDITOR
cursorCurrent.y += 2;
ImGui::SetCursorPos(cursorCurrent);
ImGui::TextUnformatted(label.c_str());
}
void ElementDrawer::DrawMeshNormal(Mesh *element, const bool &debug, const std::string &label)
{
ImGui::Text(("Name: " + element->name).c_str());
if(debug)
{
//ImGui::TextDisabled(("ID: " + to_string(element->id)).c_str());
}
ImGui::Separator();
ImGui::Text(("Vectex Count: " + std::to_string(element->vertecies.size())).c_str());
ImGui::Text(("Index Count: " + std::to_string(element->indecies.size())).c_str());
ImGui::Text(("UV Count: " + std::to_string(element->uvs.size())).c_str());
ImGui::Indent(20.0f);
if(ImGui::CollapsingHeader("Vertecies"))
{
ImGui::PushID("Verts");
ImGui::BeginDisabled();
for (int i = 0; i < element->vertecies.size(); i++)
{
ImGui::InputFloat3(std::to_string(i).c_str(), &element->vertecies[i].x);
}
ImGui::EndDisabled();
ImGui::PopID();
}
if(ImGui::CollapsingHeader("Indecies"))
{
ImGui::PushID("Inds");
ImGui::BeginDisabled();
for (int i = 0; i < element->indecies.size(); i++)
{
int val = element->indecies[i];
ImGui::InputInt(std::to_string(i).c_str(), &val);
}
ImGui::EndDisabled();
ImGui::PopID();
}
if(ImGui::CollapsingHeader("UVs"))
{
ImGui::PushID("Uvs");
ImGui::BeginDisabled();
for (int i = 0; i < element->uvs.size(); i++)
{
ImGui::InputFloat2(std::to_string(i).c_str(), &element->uvs[i].x);
}
ImGui::EndDisabled();
ImGui::PopID();
}
ImGui::Unindent(20.0f);
}
void ElementDrawer::DrawSpriteCompact(Sprite *element, const bool &debug, const std::string &label)
@@ -1215,6 +1342,98 @@ namespace TSE::EDITOR
ImVec2 texSize (available_width, (available_width) * ymultiplyer);
ImGui::Image(element->GetTextureId(), texSize, {0,1}, {1,0});
ImGui::Separator();
ImGui::SeparatorText("Import Settings");
TextureImportSettings& settings = element->GetImportSettings();
bool importSettingsChanged = false;
int importMode = static_cast<int>(settings.importMode);
const char* importModes[] = { "Raw Texture", "Sprite", "Sprite Set", "Nine Tile" };
if (ImGui::Combo("Import Mode", &importMode, importModes, IM_ARRAYSIZE(importModes)))
{
settings.importMode = static_cast<byte>(std::clamp(importMode, 0, IM_ARRAYSIZE(importModes) - 1));
importSettingsChanged = true;
}
int importCount[2] = {
static_cast<int>(settings.importCount.x),
static_cast<int>(settings.importCount.y)
};
if (ImGui::InputInt2("Import Count", importCount))
{
settings.importCount.x = static_cast<float>(std::max(1, importCount[0]));
settings.importCount.y = static_cast<float>(std::max(1, importCount[1]));
importSettingsChanged = true;
}
int bpp = static_cast<int>(settings.bpp);
if (ImGui::InputInt("BPP", &bpp))
{
settings.bpp = static_cast<uint>(std::max(0, bpp));
importSettingsChanged = true;
}
int chanels = static_cast<int>(settings.chanels);
if (ImGui::InputInt("Chanels", &chanels))
{
settings.chanels = static_cast<byte>(std::clamp(chanels, 0, 255));
importSettingsChanged = true;
}
if (InputText("Import Name", settings.name))
{
importSettingsChanged = true;
}
if (importSettingsChanged)
{
element->SaveAsset();
}
}
void ElementDrawer::DrawMaterialCompact(Material *element, const bool &debug, const std::string &label)
{
if(element == nullptr)
{
ImGui::Text("No Mesh Set");
return;
}
ImGui::Text(element->GetName().c_str());
}
void ElementDrawer::DrawMeshField(Mesh **element)
{
if(!BeginAssetField<Mesh>(element, "MeshAssetPicker"))
{
return;
}
DrawMeshCompact(*element, false, "");
OpenAssetPropertiesOnDoubleClick(*element);
EndAssetField<Mesh>();
AssetSelector<Mesh>(element, "MeshAssetPicker", "Search Mesh", "No meshes found", [](Mesh* mesh)
{
return mesh->name.empty() ? std::string("Unnamed Mesh") : mesh->name;
});
}
void ElementDrawer::DrawMaterialField(Material **element)
{
if(!BeginAssetField<Material>(element, "MaterialAssetPicker"))
{
return;
}
DrawMaterialCompact(*element, false, "");
OpenAssetPropertiesOnDoubleClick(*element);
EndAssetField<Material>();
AssetSelector<Material>(element, "MaterialAssetPicker", "Search Material", "No materials found", [](Material* mat)
{
return mat->GetName().empty() ? std::string("Unnamed Material") : mat->GetName();
});
}
} // namespace TSE::EDITOR

View File

@@ -23,7 +23,8 @@ namespace TSE::EDITOR
None,
Transformable,
Scene,
Layer
Layer,
IAsset
};
struct Inspectable
@@ -54,6 +55,9 @@ namespace TSE::EDITOR
case InspectableType::Layer:
Draw(static_cast<Layer*>(element.ptr), debug);
break;
case InspectableType::IAsset:
Draw(static_cast<IAsset*>(element.ptr), debug);
break;
default:
TSE_WARNING("Draw not implemented for this type.");
break;
@@ -83,6 +87,7 @@ namespace TSE::EDITOR
static void Draw(ParticleSystem* element, const bool& debug);
static void Draw(TileMap* element, const bool& debug);
static void Draw(OrdererSpriteSet* element, const bool& debug);
static void Draw(IAsset* element, const bool& debug);
static void DrawAudioClipCompact(AudioClip* element, const bool& debug, const std::string& label);
static void DrawAudioClipNormal(AudioClip* element, const bool& debug, const std::string& label);
@@ -94,5 +99,9 @@ namespace TSE::EDITOR
static void DrawSpriteNormal(Sprite* element, const bool& debug, const std::string& label);
static void DrawTextureCompact(Texture* element, const bool& debug, const std::string& label);
static void DrawTextureNormal(Texture* element, const bool& debug, const std::string& label);
static void DrawMaterialCompact(Material* element, const bool& debug, const std::string& label);
static void DrawMeshField(Mesh** element);
static void DrawMaterialField(Material** element);
};
} // namespace TSE::EDITOR

View File

@@ -0,0 +1,751 @@
#include "AssetsView.hpp"
#include "elements/AssetLibrary.hpp"
#include "PathHelper.hpp"
#include "json.hpp"
#include "uuid.h"
#include "elements/Material.hpp"
#include "elements/Texture.hpp"
#include "PropertiesView.hpp"
#include <algorithm>
#include <cstdio>
#include <filesystem>
#include <vector>
namespace
{
std::vector<std::filesystem::directory_entry> GetDirectories(const std::filesystem::path& folderPath)
{
std::vector<std::filesystem::directory_entry> directories;
std::error_code errorCode;
for(const auto& entry : std::filesystem::directory_iterator(folderPath, errorCode))
{
std::error_code entryErrorCode;
if(entry.is_directory(entryErrorCode))
directories.push_back(entry);
}
std::sort(directories.begin(), directories.end(), [](const auto& a, const auto& b)
{
return a.path().filename().string() < b.path().filename().string();
});
return directories;
}
std::vector<std::filesystem::directory_entry> GetFiles(const std::filesystem::path& folderPath)
{
std::vector<std::filesystem::directory_entry> files;
std::error_code errorCode;
for(const auto& entry : std::filesystem::directory_iterator(folderPath, errorCode))
{
std::error_code entryErrorCode;
if(entry.is_regular_file(entryErrorCode) && entry.path().extension() != ".meta")
files.push_back(entry);
}
std::sort(files.begin(), files.end(), [](const auto& a, const auto& b)
{
return a.path().filename().string() < b.path().filename().string();
});
return files;
}
bool IsPathInFolder(const std::filesystem::path& path, const std::filesystem::path& folderPath)
{
if(path.empty() || folderPath.empty())
return false;
const std::filesystem::path relativePath = path.lexically_relative(folderPath);
if(relativePath.empty() || relativePath == ".")
return true;
const auto firstPart = relativePath.begin();
return firstPart != relativePath.end() && firstPart->string() != "..";
}
bool IsSameOrChildPath(const std::filesystem::path& path, const std::filesystem::path& possibleParentPath)
{
return IsPathInFolder(path.lexically_normal(), possibleParentPath.lexically_normal());
}
std::string FitTextToWidth(const std::string& text, float maxWidth)
{
if(ImGui::CalcTextSize(text.c_str()).x <= maxWidth)
return text;
std::string result = text;
while(result.size() > 3)
{
result.pop_back();
const std::string truncated = result + "...";
if(ImGui::CalcTextSize(truncated.c_str()).x <= maxWidth)
return truncated;
}
return "...";
}
}
TSE::EDITOR::AssetsView::AssetsView() : GuiWindow("Assets", ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoScrollbar)
{
}
void TSE::EDITOR::AssetsView::Define()
{
const float splitterWidth = 1.0f;
const float minPanelWidth = 100.0f;
const ImVec2 availableRegion = ImGui::GetContentRegionAvail();
if(availableRegion.x <= splitterWidth)
return;
if(leftPanelWidth < 0.0f)
leftPanelWidth = availableRegion.x / 3.0f;
float maxLeftPanelWidth = availableRegion.x - splitterWidth - minPanelWidth;
if(maxLeftPanelWidth < minPanelWidth)
maxLeftPanelWidth = (availableRegion.x - splitterWidth) * 0.5f;
if(leftPanelWidth < minPanelWidth)
leftPanelWidth = minPanelWidth;
if(leftPanelWidth > maxLeftPanelWidth)
leftPanelWidth = maxLeftPanelWidth;
if(leftPanelWidth < 1.0f)
leftPanelWidth = 1.0f;
auto clampLeftPanelWidth = [&]()
{
if(leftPanelWidth < minPanelWidth)
leftPanelWidth = minPanelWidth;
if(leftPanelWidth > maxLeftPanelWidth)
leftPanelWidth = maxLeftPanelWidth;
if(leftPanelWidth < 1.0f)
leftPanelWidth = 1.0f;
};
ImGuiWindowFlags childFlags = ImGuiWindowFlags_None;
if(ImGui::BeginChild("##AssetsTreeView", {leftPanelWidth, 0.0f}, ImGuiChildFlags_Borders, childFlags))
{
DefineTreeView();
}
ImGui::EndChild();
ImGui::SameLine(0.0f, 0.0f);
ImGui::InvisibleButton("##AssetsSplitter", {splitterWidth, availableRegion.y});
if(ImGui::IsItemActive())
{
leftPanelWidth += ImGui::GetIO().MouseDelta.x;
clampLeftPanelWidth();
}
if(ImGui::IsItemHovered() || ImGui::IsItemActive())
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
ImU32 splitterColor = ImGui::GetColorU32(ImGuiCol_Separator);
if(ImGui::IsItemHovered())
splitterColor = ImGui::GetColorU32(ImGuiCol_SeparatorHovered);
if(ImGui::IsItemActive())
splitterColor = ImGui::GetColorU32(ImGuiCol_SeparatorActive);
ImGui::GetWindowDrawList()->AddRectFilled(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), splitterColor);
ImGui::SameLine(0.0f, 0.0f);
if(ImGui::BeginChild("##AssetsItemView", {0.0f, 0.0f}, ImGuiChildFlags_Borders, childFlags))
{
DefineItemView();
}
ImGui::EndChild();
}
void TSE::EDITOR::AssetsView::DefineTreeView()
{
const bool assetsOpen = ImGui::CollapsingHeader("Assets", ImGuiTreeNodeFlags_DefaultOpen);
const std::filesystem::path assetPath = TSE::AssetLibrary::GetCurrentAssetPath();
std::error_code errorCode;
if(assetPath.empty() || !std::filesystem::exists(assetPath, errorCode) || !std::filesystem::is_directory(assetPath, errorCode))
{
DefineFolderNamePopup();
return;
}
AcceptAssetDrop(assetPath);
if(assetsOpen)
{
DisplayDirectoryTree(assetPath);
ImVec2 remainingSpace = ImGui::GetContentRegionAvail();
if(remainingSpace.y > 0.0f)
{
if(remainingSpace.x < 1.0f)
remainingSpace.x = 1.0f;
ImGui::InvisibleButton("##AssetsRootDropArea", remainingSpace);
AcceptAssetDrop(assetPath);
if(ImGui::BeginPopupContextItem("##AssetsRootContext"))
{
DefineFolderBackgroundContextMenu(assetPath);
ImGui::EndPopup();
}
}
}
DefineFolderNamePopup();
}
void TSE::EDITOR::AssetsView::DefineItemView()
{
DefineItemViewToolbar();
const std::filesystem::path browserPath = GetCurrentBrowserPath();
std::error_code errorCode;
if(browserPath.empty() || !std::filesystem::exists(browserPath, errorCode) || !std::filesystem::is_directory(browserPath, errorCode))
return;
if(ImGui::BeginChild("##AssetsItemScroll", {0.0f, 0.0f}, ImGuiChildFlags_None, ImGuiWindowFlags_AlwaysVerticalScrollbar))
{
if(itemViewMode == ItemViewMode::List)
DefineItemListView(browserPath);
else
DefineItemTileView(browserPath);
ImVec2 remainingSpace = ImGui::GetContentRegionAvail();
if(remainingSpace.y > 0.0f)
{
if(remainingSpace.x < 1.0f)
remainingSpace.x = 1.0f;
ImGui::InvisibleButton("##AssetsItemRootDropArea", remainingSpace);
AcceptAssetDrop(browserPath);
if(ImGui::BeginPopupContextItem("##AssetsItemRootContext"))
{
DefineFolderBackgroundContextMenu(browserPath);
ImGui::EndPopup();
}
}
if(ImGui::BeginPopupContextWindow("##AssetsItemBackgroundContext", ImGuiPopupFlags_MouseButtonRight | ImGuiPopupFlags_NoOpenOverItems))
{
DefineFolderBackgroundContextMenu(browserPath);
ImGui::EndPopup();
}
}
ImGui::EndChild();
}
void TSE::EDITOR::AssetsView::DefineItemViewToolbar()
{
const float buttonWidth = 64.0f;
const float totalWidth = buttonWidth * 2.0f + ImGui::GetStyle().ItemSpacing.x;
const float startX = ImGui::GetContentRegionAvail().x - totalWidth;
if(startX > 0.0f)
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + startX);
if(ImGui::Selectable("List", itemViewMode == ItemViewMode::List, 0, {buttonWidth, 0.0f}))
itemViewMode = ItemViewMode::List;
ImGui::SameLine();
if(ImGui::Selectable("Tiles", itemViewMode == ItemViewMode::Tiles, 0, {buttonWidth, 0.0f}))
itemViewMode = ItemViewMode::Tiles;
}
void TSE::EDITOR::AssetsView::DefineItemListView(const std::filesystem::path& folderPath)
{
for(const auto& folderEntry : GetDirectories(folderPath))
DefineFolderListItem(folderEntry);
for(const auto& fileEntry : GetFiles(folderPath))
DefineFileListItem(fileEntry);
}
void TSE::EDITOR::AssetsView::DefineItemTileView(const std::filesystem::path& folderPath)
{
const float tileSize = 96.0f;
const float spacing = ImGui::GetStyle().ItemSpacing.x;
const float availableWidth = ImGui::GetContentRegionAvail().x;
int columns = static_cast<int>(availableWidth / (tileSize + spacing));
if(columns < 1)
columns = 1;
int index = 0;
auto nextTile = [&]()
{
index++;
if(index % columns != 0)
ImGui::SameLine();
};
for(const auto& folderEntry : GetDirectories(folderPath))
{
DefineFolderTileItem(folderEntry);
nextTile();
}
for(const auto& fileEntry : GetFiles(folderPath))
{
DefineFileTileItem(fileEntry);
nextTile();
}
}
void TSE::EDITOR::AssetsView::DefineFolderListItem(const std::filesystem::directory_entry& folderEntry)
{
const std::filesystem::path folderPath = folderEntry.path();
ImGui::PushID(folderPath.string().c_str());
ImGui::TextUnformatted("[D]");
ImGui::SameLine();
const bool selected = currentSelectedPath == folderPath;
ImGui::Selectable(folderPath.filename().string().c_str(), selected, 0, {ImGui::GetContentRegionAvail().x, 0.0f});
if(ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left))
currentSelectedPath = folderPath;
if(ImGui::BeginDragDropSource())
{
const std::string payloadPath = folderPath.string();
ImGui::SetDragDropPayload("ASSET_FOLDER_PATH", payloadPath.c_str(), payloadPath.size() + 1);
ImGui::Text("%s", folderPath.filename().string().c_str());
ImGui::EndDragDropSource();
}
AcceptAssetDrop(folderPath);
if(ImGui::BeginPopupContextItem())
{
DefineFolderContextMenu(folderPath);
ImGui::EndPopup();
}
ImGui::PopID();
}
void TSE::EDITOR::AssetsView::DefineFileListItem(const std::filesystem::directory_entry& fileEntry)
{
const std::filesystem::path filePath = fileEntry.path();
ImGui::PushID(filePath.string().c_str());
ImGui::TextUnformatted("[F]");
ImGui::SameLine();
ImGui::Selectable(filePath.filename().string().c_str(), false, 0, {ImGui::GetContentRegionAvail().x, 0.0f});
if(ImGui::IsItemClicked(ImGuiMouseButton_Left))
OpenFileProperties(filePath.string());
if(ImGui::BeginDragDropSource())
{
const std::string payloadPath = filePath.string();
ImGui::SetDragDropPayload("ASSET_FILE_PATH", payloadPath.c_str(), payloadPath.size() + 1);
ImGui::Text("%s", filePath.filename().string().c_str());
ImGui::EndDragDropSource();
}
if(ImGui::BeginPopupContextItem())
{
DefineFileContextMenu(filePath);
ImGui::EndPopup();
}
ImGui::PopID();
}
void TSE::EDITOR::AssetsView::DefineFolderTileItem(const std::filesystem::directory_entry& folderEntry)
{
const std::filesystem::path folderPath = folderEntry.path();
const float tileSize = 96.0f;
ImGui::PushID(folderPath.string().c_str());
const bool selected = currentSelectedPath == folderPath;
ImGui::Selectable("##FolderTile", selected, 0, {tileSize, tileSize});
if(ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left))
currentSelectedPath = folderPath;
ImVec2 itemMin = ImGui::GetItemRectMin();
ImVec2 itemMax = ImGui::GetItemRectMax();
ImDrawList* drawList = ImGui::GetWindowDrawList();
const std::string icon = "[D]";
const std::string name = FitTextToWidth(folderPath.filename().string(), tileSize - 12.0f);
const ImVec2 iconSize = ImGui::CalcTextSize(icon.c_str());
const ImVec2 nameSize = ImGui::CalcTextSize(name.c_str());
drawList->AddText({itemMin.x + (tileSize - iconSize.x) * 0.5f, itemMin.y + 22.0f}, ImGui::GetColorU32(ImGuiCol_Text), icon.c_str());
drawList->AddText({itemMin.x + (tileSize - nameSize.x) * 0.5f, itemMax.y - 28.0f}, ImGui::GetColorU32(ImGuiCol_Text), name.c_str());
if(ImGui::BeginDragDropSource())
{
const std::string payloadPath = folderPath.string();
ImGui::SetDragDropPayload("ASSET_FOLDER_PATH", payloadPath.c_str(), payloadPath.size() + 1);
ImGui::Text("%s", folderPath.filename().string().c_str());
ImGui::EndDragDropSource();
}
AcceptAssetDrop(folderPath);
if(ImGui::BeginPopupContextItem())
{
DefineFolderContextMenu(folderPath);
ImGui::EndPopup();
}
ImGui::PopID();
}
void TSE::EDITOR::AssetsView::DefineFileTileItem(const std::filesystem::directory_entry& fileEntry)
{
const std::filesystem::path filePath = fileEntry.path();
const float tileSize = 96.0f;
ImGui::PushID(filePath.string().c_str());
ImGui::Selectable("##FileTile", false, 0, {tileSize, tileSize});
if(ImGui::IsItemClicked(ImGuiMouseButton_Left))
OpenFileProperties(filePath.string());
ImVec2 itemMin = ImGui::GetItemRectMin();
ImVec2 itemMax = ImGui::GetItemRectMax();
ImDrawList* drawList = ImGui::GetWindowDrawList();
const std::string icon = "[F]";
const std::string name = FitTextToWidth(filePath.filename().string(), tileSize - 12.0f);
const ImVec2 iconSize = ImGui::CalcTextSize(icon.c_str());
const ImVec2 nameSize = ImGui::CalcTextSize(name.c_str());
drawList->AddText({itemMin.x + (tileSize - iconSize.x) * 0.5f, itemMin.y + 22.0f}, ImGui::GetColorU32(ImGuiCol_Text), icon.c_str());
drawList->AddText({itemMin.x + (tileSize - nameSize.x) * 0.5f, itemMax.y - 28.0f}, ImGui::GetColorU32(ImGuiCol_Text), name.c_str());
if(ImGui::BeginDragDropSource())
{
const std::string payloadPath = filePath.string();
ImGui::SetDragDropPayload("ASSET_FILE_PATH", payloadPath.c_str(), payloadPath.size() + 1);
ImGui::Text("%s", filePath.filename().string().c_str());
ImGui::EndDragDropSource();
}
if(ImGui::BeginPopupContextItem())
{
DefineFileContextMenu(filePath);
ImGui::EndPopup();
}
ImGui::PopID();
}
void TSE::EDITOR::AssetsView::DisplayDirectoryTree(const std::filesystem::path& folderPath)
{
std::vector<std::filesystem::directory_entry> childDirectories = GetDirectories(folderPath);
for(const auto& childDirectory : childDirectories)
{
const std::filesystem::path childPath = childDirectory.path();
const std::vector<std::filesystem::directory_entry> grandChildDirectories = GetDirectories(childPath);
ImGui::PushID(childPath.string().c_str());
ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_SpanFullWidth;
if(grandChildDirectories.empty())
flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen;
if(currentSelectedPath == childPath)
flags |= ImGuiTreeNodeFlags_Selected;
const bool open = ImGui::TreeNodeEx(childPath.filename().string().c_str(), flags);
if(ImGui::IsItemClicked(ImGuiMouseButton_Left) && !ImGui::IsItemToggledOpen())
currentSelectedPath = childPath;
if(ImGui::BeginDragDropSource())
{
const std::string payloadPath = childPath.string();
ImGui::SetDragDropPayload("ASSET_FOLDER_PATH", payloadPath.c_str(), payloadPath.size() + 1);
ImGui::Text("%s", childPath.filename().string().c_str());
ImGui::EndDragDropSource();
}
AcceptAssetDrop(childPath);
bool folderDeleted = false;
if(ImGui::BeginPopupContextItem())
{
folderDeleted = DefineFolderContextMenu(childPath);
ImGui::EndPopup();
}
if(open && !grandChildDirectories.empty())
{
if(!folderDeleted)
DisplayDirectoryTree(childPath);
ImGui::TreePop();
}
ImGui::PopID();
}
}
bool TSE::EDITOR::AssetsView::DefineFolderContextMenu(const std::filesystem::path& folderPath)
{
if(ImGui::MenuItem("Rename"))
RequestFolderNamePopup(FolderPopupPurpose::Rename, folderPath);
bool folderDeleted = false;
if(ImGui::MenuItem("Delete"))
{
DeleteFolder(folderPath);
folderDeleted = true;
}
if(ImGui::MenuItem("New Folder"))
RequestFolderNamePopup(FolderPopupPurpose::NewFolder, folderPath);
return folderDeleted;
}
void TSE::EDITOR::AssetsView::DefineFolderBackgroundContextMenu(const std::filesystem::path& folderPath)
{
if(ImGui::MenuItem("New Folder"))
RequestFolderNamePopup(FolderPopupPurpose::NewFolder, folderPath);
}
bool TSE::EDITOR::AssetsView::DefineFileContextMenu(const std::filesystem::path& filePath)
{
if(ImGui::MenuItem("Rename"))
RequestFolderNamePopup(FolderPopupPurpose::Rename, filePath);
bool fileDeleted = false;
if(ImGui::MenuItem("Delete"))
{
DeleteFile(filePath);
fileDeleted = true;
}
return fileDeleted;
}
void TSE::EDITOR::AssetsView::AcceptAssetDrop(const std::filesystem::path& targetFolderPath)
{
if(ImGui::BeginDragDropTarget())
{
const ImGuiPayload* folderPayload = ImGui::AcceptDragDropPayload("ASSET_FOLDER_PATH");
if(folderPayload != nullptr && folderPayload->Data != nullptr)
{
const char* payloadPath = static_cast<const char*>(folderPayload->Data);
MoveFolder(std::filesystem::path(payloadPath), targetFolderPath);
}
const ImGuiPayload* filePayload = ImGui::AcceptDragDropPayload("ASSET_FILE_PATH");
if(filePayload != nullptr && filePayload->Data != nullptr)
{
const char* payloadPath = static_cast<const char*>(filePayload->Data);
MoveFile(std::filesystem::path(payloadPath), targetFolderPath);
}
ImGui::EndDragDropTarget();
}
}
void TSE::EDITOR::AssetsView::MoveFolder(const std::filesystem::path& sourceFolderPath, const std::filesystem::path& targetFolderPath)
{
const std::filesystem::path sourcePath = sourceFolderPath.lexically_normal();
const std::filesystem::path targetPath = targetFolderPath.lexically_normal();
if(sourcePath.empty() || targetPath.empty())
return;
if(sourcePath.parent_path() == targetPath)
return;
if(IsSameOrChildPath(targetPath, sourcePath))
return;
const std::filesystem::path newFolderPath = targetPath / sourcePath.filename();
std::error_code errorCode;
if(std::filesystem::exists(newFolderPath, errorCode))
return;
std::filesystem::rename(sourcePath, newFolderPath, errorCode);
if(errorCode)
return;
if(currentSelectedPath == sourcePath)
currentSelectedPath = newFolderPath;
else if(IsSameOrChildPath(currentSelectedPath, sourcePath))
currentSelectedPath = newFolderPath / currentSelectedPath.lexically_relative(sourcePath);
TSE::AssetLibrary::RescanAssets();
}
void TSE::EDITOR::AssetsView::MoveFile(const std::filesystem::path& sourceFilePath, const std::filesystem::path& targetFolderPath)
{
const std::filesystem::path sourcePath = sourceFilePath.lexically_normal();
const std::filesystem::path targetPath = targetFolderPath.lexically_normal();
if(sourcePath.empty() || targetPath.empty())
return;
if(sourcePath.parent_path() == targetPath)
return;
const std::filesystem::path newFilePath = targetPath / sourcePath.filename();
std::error_code errorCode;
if(std::filesystem::exists(newFilePath, errorCode))
return;
std::filesystem::rename(sourcePath, newFilePath, errorCode);
if(!errorCode)
TSE::AssetLibrary::RescanAssets();
}
void TSE::EDITOR::AssetsView::RequestFolderNamePopup(FolderPopupPurpose purpose, const std::filesystem::path& folderPath)
{
folderPopupPurpose = purpose;
pendingFolderPath = folderPath;
openFolderNamePopup = true;
focusFolderNameInput = true;
const std::string defaultName = purpose == FolderPopupPurpose::Rename ? folderPath.filename().string() : "";
std::snprintf(folderNameBuffer, sizeof(folderNameBuffer), "%s", defaultName.c_str());
}
void TSE::EDITOR::AssetsView::DefineFolderNamePopup()
{
if(openFolderNamePopup)
{
openFolderNamePopup = false;
ImGui::OpenPopup("Folder Name");
}
if(ImGui::BeginPopupModal("Folder Name", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
{
ImGui::Text("Name:");
if(focusFolderNameInput)
{
ImGui::SetKeyboardFocusHere();
focusFolderNameInput = false;
}
ImGui::InputText("##FolderName", folderNameBuffer, sizeof(folderNameBuffer));
const bool inputValid = folderNameBuffer[0] != '\0';
if(!inputValid)
ImGui::TextColored(ImVec4(1, 0, 0, 1), "Not Valid.");
if(!inputValid)
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f);
if(ImGui::Button("OK", ImVec2(120, 0)) && inputValid)
{
ConfirmFolderNamePopup();
ImGui::CloseCurrentPopup();
}
if(!inputValid)
ImGui::PopStyleVar();
ImGui::SameLine();
if(ImGui::Button("Cancel", ImVec2(120, 0)))
{
folderPopupPurpose = FolderPopupPurpose::None;
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
void TSE::EDITOR::AssetsView::ConfirmFolderNamePopup()
{
const std::string folderName = folderNameBuffer;
if(folderName.empty())
return;
std::error_code errorCode;
if(folderPopupPurpose == FolderPopupPurpose::Rename)
{
const std::filesystem::path newFolderPath = pendingFolderPath.parent_path() / folderName;
if(newFolderPath != pendingFolderPath && !std::filesystem::exists(newFolderPath, errorCode))
{
const bool selectedPathIsInRenamedFolder = IsSameOrChildPath(currentSelectedPath, pendingFolderPath);
const std::filesystem::path selectedRelativePath = selectedPathIsInRenamedFolder ? currentSelectedPath.lexically_relative(pendingFolderPath) : std::filesystem::path();
std::filesystem::rename(pendingFolderPath, newFolderPath, errorCode);
if(!errorCode)
{
if(selectedPathIsInRenamedFolder)
currentSelectedPath = newFolderPath / selectedRelativePath;
TSE::AssetLibrary::RescanAssets();
}
}
}
else if(folderPopupPurpose == FolderPopupPurpose::NewFolder)
{
const std::filesystem::path newFolderPath = pendingFolderPath / folderName;
if(!std::filesystem::exists(newFolderPath, errorCode))
{
std::filesystem::create_directory(newFolderPath, errorCode);
if(!errorCode)
{
currentSelectedPath = newFolderPath;
TSE::AssetLibrary::RescanAssets();
}
}
}
folderPopupPurpose = FolderPopupPurpose::None;
}
void TSE::EDITOR::AssetsView::DeleteFolder(const std::filesystem::path& folderPath)
{
std::error_code errorCode;
std::filesystem::remove_all(folderPath, errorCode);
if(!errorCode && IsPathInFolder(currentSelectedPath, folderPath))
currentSelectedPath.clear();
if(!errorCode)
TSE::AssetLibrary::RescanAssets();
}
void TSE::EDITOR::AssetsView::DeleteFile(const std::filesystem::path& filePath)
{
std::error_code errorCode;
std::filesystem::remove(filePath, errorCode);
if(!errorCode)
TSE::AssetLibrary::RescanAssets();
}
std::filesystem::path TSE::EDITOR::AssetsView::GetCurrentBrowserPath() const
{
std::error_code errorCode;
if(!currentSelectedPath.empty() && std::filesystem::exists(currentSelectedPath, errorCode) && std::filesystem::is_directory(currentSelectedPath, errorCode))
return currentSelectedPath;
return TSE::AssetLibrary::GetCurrentAssetPath();
}
void TSE::EDITOR::AssetsView::OpenFileProperties(string path)
{
string metapath = path + ".meta";
if(FileExists(metapath))
{
using json = nlohmann::ordered_json;
std::ifstream metaStream;
OpenFileReading(metaStream, metapath);
json meta = json::parse(metaStream);
uuids::uuid id = uuids::uuid::from_string((string)meta["id"]).value_or(id);
if(!AssetLibrary::HasValue(id)) return;
IAsset* assetPtr = nullptr;
if(meta["assetType"] == "material")
{
assetPtr = static_cast<IAsset*>(AssetLibrary::GetValue<Material*>(id));
}
else if(meta["assetType"] == "texture")
{
assetPtr = static_cast<IAsset*>(AssetLibrary::GetValue<Texture*>(id));
}
if(assetPtr != nullptr)
{
PropertiesView::SetInspectorElement(InspectableType::IAsset, assetPtr);
}
}
}

View File

@@ -0,0 +1,62 @@
#pragma once
#include "UI/base/GuiWindow.h"
#include <filesystem>
namespace TSE::EDITOR
{
class AssetsView : public GuiWindow
{
private:
enum class FolderPopupPurpose
{
None,
Rename,
NewFolder
};
enum class ItemViewMode
{
List,
Tiles
};
float leftPanelWidth = -1.0f;
ItemViewMode itemViewMode = ItemViewMode::List;
std::filesystem::path currentSelectedPath;
std::filesystem::path pendingFolderPath;
FolderPopupPurpose folderPopupPurpose = FolderPopupPurpose::None;
bool openFolderNamePopup = false;
bool focusFolderNameInput = false;
char folderNameBuffer[256] = "";
void DefineTreeView();
void DefineItemView();
void DefineItemViewToolbar();
void DefineItemListView(const std::filesystem::path& folderPath);
void DefineItemTileView(const std::filesystem::path& folderPath);
void DefineFolderListItem(const std::filesystem::directory_entry& folderEntry);
void DefineFileListItem(const std::filesystem::directory_entry& fileEntry);
void DefineFolderTileItem(const std::filesystem::directory_entry& folderEntry);
void DefineFileTileItem(const std::filesystem::directory_entry& fileEntry);
void DisplayDirectoryTree(const std::filesystem::path& folderPath);
bool DefineFolderContextMenu(const std::filesystem::path& folderPath);
void DefineFolderBackgroundContextMenu(const std::filesystem::path& folderPath);
bool DefineFileContextMenu(const std::filesystem::path& filePath);
void AcceptAssetDrop(const std::filesystem::path& targetFolderPath);
void MoveFolder(const std::filesystem::path& sourceFolderPath, const std::filesystem::path& targetFolderPath);
void MoveFile(const std::filesystem::path& sourceFilePath, const std::filesystem::path& targetFolderPath);
void RequestFolderNamePopup(FolderPopupPurpose purpose, const std::filesystem::path& folderPath);
void DefineFolderNamePopup();
void ConfirmFolderNamePopup();
void DeleteFolder(const std::filesystem::path& folderPath);
void DeleteFile(const std::filesystem::path& filePath);
std::filesystem::path GetCurrentBrowserPath() const;
void OpenFileProperties(string path);
public:
AssetsView();
void Define() override;
};
} // namespace TSE::EDITOR