2021SC@SDUSC
开源游戏引擎Overload代码分析六:OvEditor——Asset相关
前言
这是Overload引擎相关的第八篇文章,同时也是OvEditor分析的第三篇。Overload引擎的Github主页在这里。
本篇文章将会介绍OvEditor的Panels文件夹中与Asset相关的一部分文件,具体应该会涉及AssetBrowser,AssetProperties和AssetView,内容可能不少。
一、AssetBrowser
1.AssetBrowser.h
class AssetBrowser : public OvUI::Panels::PanelWindow
{
public:
AssetBrowser
(
const std::string& p_title,
bool p_opened,
const OvUI::Settings::PanelWindowSettings& p_windowSettings,
const std::string& p_engineAssetFolder = "",
const std::string& p_projectAssetFolder = "",
const std::string& p_projectScriptFolder = ""
);
void Fill();
void Clear();
void Refresh();
private:
void ParseFolder(OvUI::Widgets::Layout::TreeNode& p_root, const std::filesystem::directory_entry& p_directory, bool p_isEngineItem, bool p_scriptFolder = false);
void ConsiderItem(OvUI::Widgets::Layout::TreeNode* p_root, const std::filesystem::directory_entry& p_entry, bool p_isEngineItem, bool p_autoOpen = false, bool p_scriptFolder = false);
public:
static const std::string __FILENAMES_CHARS;
private:
std::string m_engineAssetFolder;
std::string m_projectAssetFolder;
std::string m_projectScriptFolder;
OvUI::Widgets::Layout::Group* m_assetList;
std::unordered_map<OvUI::Widgets::Layout::TreeNode*, std::string> m_pathUpdate;
};
头文件定义了类,具体实现在下面讲。
2.AssetBrowser.cpp
在这里按代码排列顺序讲解各个函数功能。
const std::string FILENAMES_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-_=+ 0123456789()[]";
此字符串用于匹配文件名中的字符。
std::string GetAssociatedMetaFile(const std::string& p_assetPath)
{
return p_assetPath + ".meta";
}
给资源路径加上“.meta”的拓展名,用于获取对应的meta文件。
void RenameAsset(const std::string& p_prev, const std::string& p_new)
{
std::filesystem::rename(p_prev, p_new);
if (const std::string previousMetaPath = GetAssociatedMetaFile(p_prev); std::filesystem::exists(previousMetaPath))
{
if (const std::string newMetaPath = GetAssociatedMetaFile(p_new); !std::filesystem::exists(newMetaPath))
{
std::filesystem::rename(previousMetaPath, newMetaPath);
}
else
{
OVLOG_ERROR(newMetaPath + " is already existing, .meta creation failed");
}
}
}
此函数用于重命名资源文件,事实上第一行就已经完成了重命名,使用的是std命名空间中原有的rename函数,会把第一个参数转换为字符串后赋给第二个参数,实际是重设了文件路径。之后的代码作用是修改相关的meta文件路径,如果旧文件名的meta文件存在,并且新的文件命名没有对应的meta文件,那就把原先的meta文件也对应地重命名。而如果新的文件名已经有对应的meta文件了,那就报错说这个本应新建meta文件存在。
void RemoveAsset(const std::string& p_toDelete)
{
std::filesystem::remove(p_toDelete);
if (const std::string metaPath = GetAssociatedMetaFile(p_toDelete); std::filesystem::exists(metaPath))
{
std::filesystem::remove(metaPath);
}
}
此函数用于移除资源,给定要移除的资源文件名,同时也移除对应的meta文件,如果对应的meta文件存在的话。
class TexturePreview : public OvUI::Plugins::IPlugin
{
public:
TexturePreview() : image(0, { 80, 80 })
{
}
void SetPath(const std::string& p_path)
{
texture = OvCore::Global::ServiceLocator::Get<OvCore::ResourceManagement::TextureManager>()[p_path];
}
virtual void Execute() override
{
if (ImGui::IsItemHovered())
{
if (texture)
image.textureID.id = texture->id;
ImGui::BeginTooltip();
image.Draw();
ImGui::EndTooltip();
}
}
OvRendering::Resources::Texture* texture = nullptr;
OvUI::Widgets::Visual::Image image;
};
定义了类TexturePreview,有构造函数,默认80x80的大小。setPath用于设置路径。Execute作为虚函数用于继承,作用是绘制预览画面。
class BrowserItemContextualMenu : public OvUI::Plugins::ContextualMenu
{
public:
BrowserItemContextualMenu(const std::string p_filePath, bool p_protected = false) : m_protected(p_protected), filePath(p_filePath) {}
virtual void CreateList()
{
if (!m_protected)
{
auto& renameMenu = CreateWidget<OvUI::Widgets::Menu::MenuList>("Rename to...");
auto& deleteAction = CreateWidget<OvUI::Widgets::Menu::MenuItem>("Delete");
auto& nameEditor = renameMenu.CreateWidget<OvUI::Widgets::InputFields::InputText>("");
nameEditor.selectAllOnClick = true;
renameMenu.ClickedEvent += [this, &nameEditor]
{
nameEditor.content = OvTools::Utils::PathParser::GetElementName(filePath);
if (!std::filesystem::is_directory(filePath))
if (size_t pos = nameEditor.content.rfind('.'); pos != std::string::npos)
nameEditor.content = nameEditor.content.substr(0, pos);
};
deleteAction.ClickedEvent += [this] { DeleteItem(); };
nameEditor.EnterPressedEvent += [this](std::string p_newName)
{
if (!std::filesystem::is_directory(filePath))
p_newName += '.' + OvTools::Utils::PathParser::GetExtension(filePath);
p_newName.erase(std::remove_if(p_newName.begin(), p_newName.end(), [](auto& c)
{
return std::find(FILENAMES_CHARS.begin(), FILENAMES_CHARS.end(), c) == FILENAMES_CHARS.end();
}), p_newName.end());
std::string containingFolderPath = OvTools::Utils::PathParser::GetContainingFolder(filePath);
std::string newPath = containingFolderPath + p_newName;
std::string oldPath = filePath;
if (filePath != newPath && !std::filesystem::exists(newPath))
filePath = newPath;
if (std::filesystem::is_directory(oldPath))
filePath += '\\';
RenamedEvent.Invoke(oldPath, newPath);
};
}
}
virtual void Execute() override
{
if (m_widgets.size() > 0)
OvUI::Plugins::ContextualMenu::Execute();
}
virtual void DeleteItem() = 0;
public:
bool m_protected;
std::string filePath;
OvTools::Eventing::Event<std::string> DestroyedEvent;
OvTools::Eventing::Event<std::string, std::string> RenamedEvent;
};
CreateList函数用于创建菜单及菜单项,同时也定义了这些菜单项点击后的作用,如创建删除等。
class FolderContextualMenu : public BrowserItemContextualMenu
{
public:
FolderContextualMenu(const std::string& p_filePath, bool p_protected = false) : BrowserItemContextualMenu(p_filePath, p_protected) {}
virtual void CreateList() override
{
auto& showInExplorer = CreateWidget<OvUI::Widgets::Menu::MenuItem>("Show in explorer");
showInExplorer.ClickedEvent += [this]
{
OvTools::Utils::SystemCalls::ShowInExplorer(filePath);
};
if (!m_protected)
{
auto& importAssetHere = CreateWidget<OvUI::Widgets::Menu::MenuItem>("Import Here...");
importAssetHere.ClickedEvent += [this]
{
if (EDITOR_EXEC(ImportAssetAtLocation(filePath)))
{
OvUI::Widgets::Layout::TreeNode* pluginOwner = reinterpret_cast<OvUI::Widgets::Layout::TreeNode*>(userData);
pluginOwner->Close();
EDITOR_EXEC(DelayAction(std::bind(&OvUI::Widgets::Layout::TreeNode::Open, pluginOwner)));
}
};
auto& createMenu = CreateWidget<OvUI::Widgets::Menu::MenuList>("Create..");
auto& createFolderMenu = createMenu.CreateWidget<OvUI::Widgets::Menu::MenuList>("Folder");
auto& createSceneMenu = createMenu.CreateWidget<OvUI::Widgets::Menu::MenuList>("Scene");
auto& createShaderMenu = createMenu.CreateWidget<OvUI::Widgets::Menu::MenuList>("Shader");
auto& createMaterialMenu = createMenu.CreateWidget<OvUI::Widgets::Menu::MenuList>("Material");
auto& createStandardShaderMenu = createShaderMenu.CreateWidget<OvUI::Widgets::Menu::MenuList>("Standard template");
auto& createStandardPBRShaderMenu = createShaderMenu.CreateWidget<OvUI::Widgets::Menu::MenuList>("Standard PBR template");
auto& createUnlitShaderMenu = createShaderMenu.CreateWidget<OvUI::Widgets::Menu::MenuList>("Unlit template");
auto& createLambertShaderMenu = createShaderMenu.CreateWidget<OvUI::Widgets::Menu::MenuList>("Lambert template");
auto& createEmptyMaterialMenu = createMaterialMenu.CreateWidget<OvUI::Widgets::Menu::MenuList>("Empty");
auto& createStandardMaterialMenu = createMaterialMenu.CreateWidget<OvUI::Widgets::Menu::MenuList>("Standard");
auto& createStandardPBRMaterialMenu = createMaterialMenu.CreateWidget<OvUI::Widgets::Menu::MenuList>("Standard PBR");
auto& createUnlitMaterialMenu = createMaterialMenu.CreateWidget<OvUI::Widgets::Menu::MenuList>("Unlit");
auto& createLambertMaterialMenu = createMaterialMenu.CreateWidget<OvUI::Widgets::Menu::MenuList>("Lambert");
auto& createFolder = createFolderMenu.CreateWidget<OvUI::Widgets::InputFields::InputText>("");
auto& createScene = createSceneMenu.CreateWidget<OvUI::Widgets::InputFields::InputText>("");
auto& createEmptyMaterial = createEmptyMaterialMenu.CreateWidget<OvUI::Widgets::InputFields::InputText>("");
auto& createStandardMaterial = createStandardMaterialMenu.CreateWidget<OvUI::Widgets::InputFields::InputText>("");
auto& createStandardPBRMaterial = createStandardPBRMaterialMenu.CreateWidget<OvUI::Widgets::InputFields::InputText>("");
auto& createUnlitMaterial = createUnlitMaterialMenu.CreateWidget<OvUI::Widgets::InputFields::InputText>("");
auto& createLambertMaterial = createLambertMaterialMenu.CreateWidget<OvUI::Widgets::InputFields::InputText>("");
auto& createStandardShader = createStandardShaderMenu.CreateWidget<OvUI::Widgets::InputFields::InputText>("");
auto& createStandardPBRShader = createStandardPBRShaderMenu.CreateWidget<OvUI::Widgets::InputFields::InputText>("");
auto& createUnlitShader = createUnlitShaderMenu.CreateWidget<OvUI::Widgets::InputFields::InputText>("");
auto& createLambertShader = createLambertShaderMenu.CreateWidget<OvUI::Widgets::InputFields::InputText>("");
createFolderMenu.ClickedEvent += [&createFolder] { createFolder.content = ""; };
createSceneMenu.ClickedEvent += [&createScene] { createScene.content = ""; };
createStandardShaderMenu.ClickedEvent += [&createStandardShader] { createStandardShader.content = ""; };
createStandardPBRShaderMenu.ClickedEvent += [&createStandardPBRShader] { createStandardPBRShader.content = ""; };
createUnlitShaderMenu.ClickedEvent += [&createUnlitShader] { createUnlitShader.content = ""; };
createLambertShaderMenu.ClickedEvent += [&createLambertShader] { createLambertShader.content = ""; };
createEmptyMaterialMenu.ClickedEvent += [&createEmptyMaterial] { createEmptyMaterial.content = ""; };
createStandardMaterialMenu.ClickedEvent += [&createStandardMaterial] { createStandardMaterial.content = ""; };
createStandardPBRMaterialMenu.ClickedEvent += [&createStandardPBRMaterial] { createStandardPBRMaterial.content = ""; };
createUnlitMaterialMenu.ClickedEvent += [&createUnlitMaterial] { createUnlitMaterial.content = ""; };
createLambertMaterialMenu.ClickedEvent += [&createLambertMaterial] { createLambertMaterial.content = ""; };
createFolder.EnterPressedEvent += [this](std::string newFolderName)
{
size_t fails = 0;
std::string finalPath;
do
{
finalPath = filePath + (!fails ? newFolderName : newFolderName + " (" + std::to_string(fails) + ')');
++fails;
} while (std::filesystem::exists(finalPath));
std::filesystem::create_directory(finalPath);
ItemAddedEvent.Invoke(finalPath);
Close();
};
createScene.EnterPressedEvent += [this](std::string newSceneName)
{
size_t fails = 0;
std::string finalPath;
do
{
finalPath = filePath + (!fails ? newSceneName : newSceneName + " (" + std::to_string(fails) + ')') + ".ovscene";
++fails;
} while (std::filesystem::exists(finalPath));
std::ofstream outfile(finalPath);
outfile << "<root><scene><actors><actor><name>Directional Light</name><tag></tag><active>true</active><id>1</id><parent>0</parent><components><component><type>class OvCore::ECS::Components::CDirectionalLight</type><data><diffuse><x>1</x><y>1</y><z>1</z></diffuse><specular><x>1</x><y>1</y><z>1</z></specular><intensity>0.75</intensity></data></component><component><type>class OvCore::ECS::Components::CTransform</type><data><position><x>0</x><y>10</y><z>0</z></position><rotation><x>0.81379771</x><y>-0.17101006</y><z>0.29619816</z><w>0.46984628</w></rotation><scale><x>1</x><y>1</y><z>1</z></scale></data></component></components><behaviours/></actor><actor><name>Ambient Light</name><tag></tag><active>true</active><id>2</id><parent>0</parent><components><component><type>class OvCore::ECS::Components::CAmbientSphereLight</type><data><ambient><x>1</x><y>1</y><z>1</z></ambient><intensity>0.1</intensity><radius>10000</radius></data></component><component><type>class OvCore::ECS::Components::CTransform</type><data><position><x>0</x><y>0</y><z>0</z></position><rotation><x>0</x><y>0</y><z>0</z><w>1</w></rotation><scale><x>1</x><y>1</y><z>1</z></scale></data></component></components><behaviours/></actor><actor><name>Main Camera</name><tag></tag><active>true</active><id>3</id><parent>0</parent><components><component><type>class OvCore::ECS::Components::CCamera</type><data><fov>45</fov><near>0.1</near><far>1000</far><clear_color><x>0.1921569</x><y>0.3019608</y><z>0.47450981</z></clear_color></data></component><component><type>class OvCore::ECS::Components::CTransform</type><data><position><x>0</x><y>3</y><z>8</z></position><rotation><x>-7.5904039e-09</x><y>0.98480773</y><z>-0.17364819</z><w>-4.3047311e-08</w></rotation><scale><x>1</x><y>1</y><z>1</z></scale></data></component></components><behaviours/></actor></actors></scene></root>" << std::endl;
ItemAddedEvent.Invoke(finalPath);
Close();
};
createStandardShader.EnterPressedEvent += [this](std::string newShaderName)
{
size_t fails = 0;
std::string finalPath;
do
{
finalPath = filePath + '\\' + (!fails ? newShaderName : newShaderName + " (" + std::to_string(fails) + ')') + ".glsl";
++fails;
} while (std::filesystem::exists(finalPath));
std::filesystem::copy_file(EDITOR_CONTEXT(engineAssetsPath) + "Shaders\\Standard.glsl", finalPath);
ItemAddedEvent.Invoke(finalPath);
Close();
};
createStandardPBRShader.EnterPressedEvent += [this](std::string newShaderName)
{
size_t fails = 0;
std::string finalPath;
do
{
finalPath = filePath + '\\' + (!fails ? newShaderName : newShaderName + " (" + std::to_string(fails) + ')') + ".glsl";
++fails;
} while (std::filesystem::exists(finalPath));
std::filesystem::copy_file(EDITOR_CONTEXT(engineAssetsPath) + "Shaders\\StandardPBR.glsl", finalPath);
ItemAddedEvent.Invoke(finalPath);
Close();
};
createUnlitShader.EnterPressedEvent += [this](std::string newShaderName)
{
size_t fails = 0;
std::string finalPath;
do
{
finalPath = filePath + '\\' + (!fails ? newShaderName : newShaderName + " (" + std::to_string(fails) + ')') + ".glsl";
++fails;
} while (std::filesystem::exists(finalPath));
std::filesystem::copy_file(EDITOR_CONTEXT(engineAssetsPath) + "Shaders\\Unlit.glsl", finalPath);
ItemAddedEvent.Invoke(finalPath);
Close();
};
createLambertShader.EnterPressedEvent += [this](std::string newShaderName)
{
size_t fails = 0;
std::string finalPath;
do
{
finalPath = filePath + '\\' + (!fails ? newShaderName : newShaderName + " (" + std::to_string(fails) + ')') + ".glsl";
++fails;
} while (std::filesystem::exists(finalPath));
std::filesystem::copy_file(EDITOR_CONTEXT(engineAssetsPath) + "Shaders\\Lambert.glsl", finalPath);
ItemAddedEvent.Invoke(finalPath);
Close();
};
createEmptyMaterial.EnterPressedEvent += [this](std::string materialName)
{
size_t fails = 0;
std::string finalPath;
do
{
finalPath = filePath + (!fails ? materialName : materialName + " (" + std::to_string(fails) + ')') + ".ovmat";
++fails;
} while (std::filesystem::exists(finalPath));
{
std::ofstream outfile(finalPath);
outfile << "<root><shader>?</shader></root>" << std::endl;
}
ItemAddedEvent.Invoke(finalPath);
if (auto instance = EDITOR_CONTEXT(materialManager)[EDITOR_EXEC(GetResourcePath(finalPath))])
{
auto& materialEditor = EDITOR_PANEL(OvEditor::Panels::MaterialEditor, "Material Editor");
materialEditor.SetTarget(*instance);
materialEditor.Open();
materialEditor.Focus();
materialEditor.Preview();
}
Close();
};
createStandardMaterial.EnterPressedEvent += [this](std::string materialName)
{
size_t fails = 0;
std::string finalPath;
do
{
finalPath = filePath + (!fails ? materialName : materialName + " (" + std::to_string(fails) + ')') + ".ovmat";
++fails;
} while (std::filesystem::exists(finalPath));
{
std::ofstream outfile(finalPath);
outfile << "<root><shader>:Shaders\\Standard.glsl</shader></root>" << std::endl;
}
ItemAddedEvent.Invoke(finalPath);
if (auto instance = EDITOR_CONTEXT(materialManager)[EDITOR_EXEC(GetResourcePath(finalPath))])
{
auto& materialEditor = EDITOR_PANEL(OvEditor::Panels::MaterialEditor, "Material Editor");
materialEditor.SetTarget(*instance);
materialEditor.Open();
materialEditor.Focus();
materialEditor.Preview();
}
Close();
};
createStandardPBRMaterial.EnterPressedEvent += [this](std::string materialName)
{
size_t fails = 0;
std::string finalPath;
do
{
finalPath = filePath + (!fails ? materialName : materialName + " (" + std::to_string(fails) + ')') + ".ovmat";
++fails;
} while (std::filesystem::exists(finalPath));
{
std::ofstream outfile(finalPath);
outfile << "<root><shader>:Shaders\\StandardPBR.glsl</shader></root>" << std::endl;
}
ItemAddedEvent.Invoke(finalPath);
if (auto instance = EDITOR_CONTEXT(materialManager)[EDITOR_EXEC(GetResourcePath(finalPath))])
{
auto& materialEditor = EDITOR_PANEL(OvEditor::Panels::MaterialEditor, "Material Editor");
materialEditor.SetTarget(*instance);
materialEditor.Open();
materialEditor.Focus();
materialEditor.Preview();
}
Close();
};
createUnlitMaterial.EnterPressedEvent += [this](std::string materialName)
{
std::string newSceneName = "Material";
size_t fails = 0;
std::string finalPath;
do
{
finalPath = filePath + (!fails ? materialName : materialName + " (" + std::to_string(fails) + ')') + ".ovmat";
++fails;
} while (std::filesystem::exists(finalPath));
{
std::ofstream outfile(finalPath);
outfile << "<root><shader>:Shaders\\Unlit.glsl</shader></root>" << std::endl;
}
ItemAddedEvent.Invoke(finalPath);
if (auto instance = EDITOR_CONTEXT(materialManager)[EDITOR_EXEC(GetResourcePath(finalPath))])
{
auto& materialEditor = EDITOR_PANEL(OvEditor::Panels::MaterialEditor, "Material Editor");
materialEditor.SetTarget(*instance);
materialEditor.Open();
materialEditor.Focus();
materialEditor.Preview();
}
Close();
};
createLambertMaterial.EnterPressedEvent += [this](std::string materialName)
{
size_t fails = 0;
std::string finalPath;
do
{
finalPath = filePath + (!fails ? materialName : materialName + " (" + std::to_string(fails) + ')') + ".ovmat";
++fails;
} while (std::filesystem::exists(finalPath));
{
std::ofstream outfile(finalPath);
outfile << "<root><shader>:Shaders\\Lambert.glsl</shader></root>" << std::endl;
}
ItemAddedEvent.Invoke(finalPath);
if (auto instance = EDITOR_CONTEXT(materialManager)[EDITOR_EXEC(GetResourcePath(finalPath))])
{
auto& materialEditor = EDITOR_PANEL(OvEditor::Panels::MaterialEditor, "Material Editor");
materialEditor.SetTarget(*instance);
materialEditor.Open();
materialEditor.Focus();
materialEditor.Preview();
}
Close();
};
BrowserItemContextualMenu::CreateList();
}
}
virtual void DeleteItem() override
{
using namespace OvWindowing::Dialogs;
MessageBox message("Delete folder", "Deleting a folder (and all its content) is irreversible, are you sure that you want to delete \"" + filePath + "\"?", MessageBox::EMessageType::WARNING, MessageBox::EButtonLayout::YES_NO);
if (message.GetUserAction() == MessageBox::EUserAction::YES)
{
if (std::filesystem::exists(filePath) == true)
{
EDITOR_EXEC(PropagateFolderDestruction(filePath));
std::filesystem::remove_all(filePath);
DestroyedEvent.Invoke(filePath);
}
}
}
public:
OvTools::Eventing::Event<std::string> ItemAddedEvent;
};
非常长的一个类定义,但实际和上面一样,还是在定义菜单及其相关的内容,只是内容多了不少。例如创建了多个菜单和子菜单,有新创建文件夹,场景,着色器,材质等的函数,实际上都是创建对应文件和路径。当然也有删除的函数,删除文件夹会把下层的文件也同步删除,不可恢复。
class ScriptFolderContextualMenu : public FolderContextualMenu
{
public:
ScriptFolderContextualMenu(const std::string& p_filePath, bool p_protected = false) : FolderContextualMenu(p_filePath, p_protected) {}
void CreateScript(const std::string& p_name, const std::string& p_path)
{
std::string fileContent = "local " + p_name + " =\n{\n}\n\nfunction " + p_name + ":OnStart()\nend\n\nfunction " + p_name + ":OnUpdate(deltaTime)\nend\n\nreturn " + p_name;
std::ofstream outfile(p_path);
outfile << fileContent << std::endl;
ItemAddedEvent.Invoke(p_path);
Close();
}
virtual void CreateList() override
{
FolderContextualMenu::CreateList();
auto& newScriptMenu = CreateWidget<OvUI::Widgets::Menu::MenuList>("New script...");
auto& nameEditor = newScriptMenu.CreateWidget<OvUI::Widgets::InputFields::InputText>("");
newScriptMenu.ClickedEvent += [this, &nameEditor]
{
nameEditor.content = OvTools::Utils::PathParser::GetElementName("");
};
nameEditor.EnterPressedEvent += [this](std::string p_newName)
{
p_newName.erase(std::remove_if(p_newName.begin(), p_newName.end(), [](auto& c)
{
return std::find(FILENAMES_CHARS.begin(), FILENAMES_CHARS.end(), c) == FILENAMES_CHARS.end();
}), p_newName.end());
std::string newPath = filePath + p_newName + ".lua";
if (!std::filesystem::exists(newPath))
{
CreateScript(p_newName, newPath);
}
};
}
};
此函数用于创建脚本文件夹,与上一个函数的区别在于会创建脚本文件,脚本文件有预制的函数,除此之外基本一样。
class FileContextualMenu : public BrowserItemContextualMenu
{
public:
FileContextualMenu(const std::string& p_filePath, bool p_protected = false) : BrowserItemContextualMenu(p_filePath, p_protected) {}
virtual void CreateList() override
{
auto& editAction = CreateWidget<OvUI::Widgets::Menu::MenuItem>("Open");
editAction.ClickedEvent += [this]
{
OvTools::Utils::SystemCalls::OpenFile(filePath);
};
if (!m_protected)
{
auto& duplicateAction = CreateWidget<OvUI::Widgets::Menu::MenuItem>("Duplicate");
duplicateAction.ClickedEvent += [this]
{
std::string filePathWithoutExtension = filePath;
if (size_t pos = filePathWithoutExtension.rfind('.'); pos != std::string::npos)
filePathWithoutExtension = filePathWithoutExtension.substr(0, pos);
std::string extension = "." + OvTools::Utils::PathParser::GetExtension(filePath);
auto filenameAvailable = [&extension](const std::string& target)
{
return !std::filesystem::exists(target + extension);
};
const auto newNameWithoutExtension = OvTools::Utils::String::GenerateUnique(filePathWithoutExtension, filenameAvailable);
std::string finalPath = newNameWithoutExtension + extension;
std::filesystem::copy(filePath, finalPath);
DuplicateEvent.Invoke(finalPath);
};
}
BrowserItemContextualMenu::CreateList();
auto& editMetadata = CreateWidget<OvUI::Widgets::Menu::MenuItem>("Properties");
editMetadata.ClickedEvent += [this]
{
auto& panel = EDITOR_PANEL(OvEditor::Panels::AssetProperties, "Asset Properties");
std::string resourcePath = EDITOR_EXEC(GetResourcePath(filePath, m_protected));
panel.SetTarget(resourcePath);
panel.Open();
panel.Focus();
};
}
virtual void DeleteItem() override
{
using namespace OvWindowing::Dialogs;
MessageBox message("Delete file", "Deleting a file is irreversible, are you sure that you want to delete \"" + filePath + "\"?", MessageBox::EMessageType::WARNING, MessageBox::EButtonLayout::YES_NO);
if (message.GetUserAction() == MessageBox::EUserAction::YES)
{
RemoveAsset(filePath);
DestroyedEvent.Invoke(filePath);
EDITOR_EXEC(PropagateFileRename(filePath, "?"));
}
}
public:
OvTools::Eventing::Event<std::string> DuplicateEvent;
};
同样是一个文件菜单的类,不过还继承了之前的BrowserItemContextualMenu,其他地方没有什么特别的。
template<typename Resource, typename ResourceLoader>
class PreviewableContextualMenu : public FileContextualMenu
{
public:
PreviewableContextualMenu(const std::string& p_filePath, bool p_protected = false) : FileContextualMenu(p_filePath, p_protected) {}
virtual void CreateList() override
{
auto& previewAction = CreateWidget<OvUI::Widgets::Menu::MenuItem>("Preview");
previewAction.ClickedEvent += [this]
{
Resource* resource = OvCore::Global::ServiceLocator::Get<ResourceLoader>()[EDITOR_EXEC(GetResourcePath(filePath, m_protected))];
auto& assetView = EDITOR_PANEL(OvEditor::Panels::AssetView, "Asset View");
assetView.SetResource(resource);
assetView.Open();
assetView.Focus();
};
FileContextualMenu::CreateList();
}
};
最大的区别在于有模板,可以兼容多种资源。继承了FileContextualMenu,实际上是对预览菜单的补充。
class ShaderContextualMenu : public FileContextualMenu
{
public:
ShaderContextualMenu(const std::string& p_filePath, bool p_protected = false) : FileContextualMenu(p_filePath, p_protected) {}
virtual void CreateList() override
{
FileContextualMenu::CreateList();
auto& compileAction = CreateWidget<OvUI::Widgets::Menu::MenuItem>("Compile");
compileAction.ClickedEvent += [this]
{
auto& shaderManager = OVSERVICE(OvCore::ResourceManagement::ShaderManager);
std::string resourcePath = EDITOR_EXEC(GetResourcePath(filePath, m_protected));
if (shaderManager.IsResourceRegistered(resourcePath))
{
OvRendering::Resources::Loaders::ShaderLoader::Recompile(*shaderManager[resourcePath], filePath);
}
else
{
OvRendering::Resources::Shader* shader = OVSERVICE(OvCore::ResourceManagement::ShaderManager)[resourcePath];
if (shader)
OVLOG_INFO("[COMPILE] \"" + filePath + "\": Success!");
}
};
}
};
继承了FileContextualMenu,是对着色器菜单的补充。
class ModelContextualMenu : public PreviewableContextualMenu<OvRendering::Resources::Model, OvCore::ResourceManagement::ModelManager>
{
public:
ModelContextualMenu(const std::string& p_filePath, bool p_protected = false) : PreviewableContextualMenu(p_filePath, p_protected) {}
virtual void CreateList() override
{
auto& reloadAction = CreateWidget<OvUI::Widgets::Menu::MenuItem>("Reload");
reloadAction.ClickedEvent += [this]
{
auto& modelManager = OVSERVICE(OvCore::ResourceManagement::ModelManager);
std::string resourcePath = EDITOR_EXEC(GetResourcePath(filePath, m_protected));
if (modelManager.IsResourceRegistered(resourcePath))
{
modelManager.AResourceManager::ReloadResource(resourcePath);
}
};
if (!m_protected)
{
auto& generateMaterialsMenu = CreateWidget<OvUI::Widgets::Menu::MenuList>("Generate materials...");
generateMaterialsMenu.CreateWidget<OvUI::Widgets::Menu::MenuItem>("Standard").ClickedEvent += [this]
{
auto& modelManager = OVSERVICE(OvCore::ResourceManagement::ModelManager);
std::string resourcePath = EDITOR_EXEC(GetResourcePath(filePath, m_protected));
if (auto model = modelManager.GetResource(resourcePath))
{
for (const std::string& materialName : model->GetMaterialNames())
{
size_t fails = 0;
std::string finalPath;
do
{
finalPath = OvTools::Utils::PathParser::GetContainingFolder(filePath) + (!fails ? materialName : materialName + " (" + std::to_string(fails) + ')') + ".ovmat";
++fails;
} while (std::filesystem::exists(finalPath));
{
std::ofstream outfile(finalPath);
outfile << "<root><shader>:Shaders\\Standard.glsl</shader></root>" << std::endl;
}
DuplicateEvent.Invoke(finalPath);
}
}
};
generateMaterialsMenu.CreateWidget<OvUI::Widgets::Menu::MenuItem>("StandardPBR").ClickedEvent += [this]
{
auto& modelManager = OVSERVICE(OvCore::ResourceManagement::ModelManager);
std::string resourcePath = EDITOR_EXEC(GetResourcePath(filePath, m_protected));
if (auto model = modelManager.GetResource(resourcePath))
{
for (const std::string& materialName : model->GetMaterialNames())
{
size_t fails = 0;
std::string finalPath;
do
{
finalPath = OvTools::Utils::PathParser::GetContainingFolder(filePath) + (!fails ? materialName : materialName + " (" + std::to_string(fails) + ')') + ".ovmat";
++fails;
} while (std::filesystem::exists(finalPath));
{
std::ofstream outfile(finalPath);
outfile << "<root><shader>:Shaders\\StandardPBR.glsl</shader></root>" << std::endl;
}
DuplicateEvent.Invoke(finalPath);
}
}
};
generateMaterialsMenu.CreateWidget<OvUI::Widgets::Menu::MenuItem>("Unlit").ClickedEvent += [this]
{
auto& modelManager = OVSERVICE(OvCore::ResourceManagement::ModelManager);
std::string resourcePath = EDITOR_EXEC(GetResourcePath(filePath, m_protected));
if (auto model = modelManager.GetResource(resourcePath))
{
for (const std::string& materialName : model->GetMaterialNames())
{
size_t fails = 0;
std::string finalPath;
do
{
finalPath = OvTools::Utils::PathParser::GetContainingFolder(filePath) + (!fails ? materialName : materialName + " (" + std::to_string(fails) + ')') + ".ovmat";
++fails;
} while (std::filesystem::exists(finalPath));
{
std::ofstream outfile(finalPath);
outfile << "<root><shader>:Shaders\\Unlit.glsl</shader></root>" << std::endl;
}
DuplicateEvent.Invoke(finalPath);
}
}
};
generateMaterialsMenu.CreateWidget<OvUI::Widgets::Menu::MenuItem>("Lambert").ClickedEvent += [this]
{
auto& modelManager = OVSERVICE(OvCore::ResourceManagement::ModelManager);
std::string resourcePath = EDITOR_EXEC(GetResourcePath(filePath, m_protected));
if (auto model = modelManager.GetResource(resourcePath))
{
for (const std::string& materialName : model->GetMaterialNames())
{
size_t fails = 0;
std::string finalPath;
do
{
finalPath = OvTools::Utils::PathParser::GetContainingFolder(filePath) + (!fails ? materialName : materialName + " (" + std::to_string(fails) + ')') + ".ovmat";
++fails;
} while (std::filesystem::exists(finalPath));
{
std::ofstream outfile(finalPath);
outfile << "<root><shader>:Shaders\\Lambert.glsl</shader></root>" << std::endl;
}
DuplicateEvent.Invoke(finalPath);
}
}
};
}
PreviewableContextualMenu::CreateList();
}
};
继承了FileContextualMenu,是对材质菜单的补充,有对各种材质的区分。
class TextureContextualMenu : public PreviewableContextualMenu<OvRendering::Resources::Texture, OvCore::ResourceManagement::TextureManager>
{
public:
TextureContextualMenu(const std::string& p_filePath, bool p_protected = false) : PreviewableContextualMenu(p_filePath, p_protected) {}
virtual void CreateList() override
{
auto& reloadAction = CreateWidget<OvUI::Widgets::Menu::MenuItem>("Reload");
reloadAction.ClickedEvent += [this]
{
auto& textureManager = OVSERVICE(OvCore::ResourceManagement::TextureManager);
std::string resourcePath = EDITOR_EXEC(GetResourcePath(filePath, m_protected));
if (textureManager.IsResourceRegistered(resourcePath))
{
textureManager.AResourceManager::ReloadResource(resourcePath);
EDITOR_PANEL(OvEditor::Panels::MaterialEditor, "Material Editor").Refresh();
}
};
PreviewableContextualMenu::CreateList();
}
};
继承了PreviewableContextualMenu,是图片相关的菜单管理,不过与前面的各种菜单没什么区别。
class SceneContextualMenu : public FileContextualMenu
{
public:
SceneContextualMenu(const std::string& p_filePath, bool p_protected = false) : FileContextualMenu(p_filePath, p_protected) {}
virtual void CreateList() override
{
auto& editAction = CreateWidget<OvUI::Widgets::Menu::MenuItem>("Edit");
editAction.ClickedEvent += [this]
{
EDITOR_EXEC(LoadSceneFromDisk(EDITOR_EXEC(GetResourcePath(filePath))));
};
FileContextualMenu::CreateList();
}
};
继承了FileContextualMenu,是对scene相关的菜单的补充。
class MaterialContextualMenu : public PreviewableContextualMenu<OvCore::Resources::Material, OvCore::ResourceManagement::MaterialManager>
{
public:
MaterialContextualMenu(const std::string& p_filePath, bool p_protected = false) : PreviewableContextualMenu(p_filePath, p_protected) {}
virtual void CreateList() override
{
auto& editAction = CreateWidget<OvUI::Widgets::Menu::MenuItem>("Edit");
editAction.ClickedEvent += [this]
{
OvCore::Resources::Material* material = OVSERVICE(OvCore::ResourceManagement::MaterialManager)[EDITOR_EXEC(GetResourcePath(filePath, m_protected))];
if (material)
{
auto& materialEditor = EDITOR_PANEL(OvEditor::Panels::MaterialEditor, "Material Editor");
materialEditor.SetTarget(*material);
materialEditor.Open();
materialEditor.Focus();
OvCore::Resources::Material* resource = OvCore::Global::ServiceLocator::Get<OvCore::ResourceManagement::MaterialManager>()[EDITOR_EXEC(GetResourcePath(filePath, m_protected))];
auto& assetView = EDITOR_PANEL(OvEditor::Panels::AssetView, "Asset View");
assetView.SetResource(resource);
assetView.Open();
assetView.Focus();
}
};
auto& reload = CreateWidget<OvUI::Widgets::Menu::MenuItem>("Reload");
reload.ClickedEvent += [this]
{
auto materialManager = OVSERVICE(OvCore::ResourceManagement::MaterialManager);
auto resourcePath = EDITOR_EXEC(GetResourcePath(filePath, m_protected));
OvCore::Resources::Material* material = materialManager[resourcePath];
if (material)
{
materialManager.AResourceManager::ReloadResource(resourcePath);
EDITOR_PANEL(OvEditor::Panels::MaterialEditor, "Material Editor").Refresh();
}
};
PreviewableContextualMenu::CreateList();
}
};
继承了PreviewableContextualMenu,可以说是材质相关的预览。
OvEditor::Panels::AssetBrowser::AssetBrowser
(
const std::string& p_title,
bool p_opened,
const OvUI::Settings::PanelWindowSettings& p_windowSettings,
const std::string& p_engineAssetFolder,
const std::string& p_projectAssetFolder,
const std::string& p_projectScriptFolder
) :
PanelWindow(p_title, p_opened, p_windowSettings),
m_engineAssetFolder(p_engineAssetFolder),
m_projectAssetFolder(p_projectAssetFolder),
m_projectScriptFolder(p_projectScriptFolder)
{
if (!std::filesystem::exists(m_projectAssetFolder))
{
std::filesystem::create_directories(m_projectAssetFolder);
OvWindowing::Dialogs::MessageBox message
(
"Assets folder not found",
"The \"Assets/\" folders hasn't been found in your project directory.\nIt has been automatically generated",
OvWindowing::Dialogs::MessageBox::EMessageType::WARNING,
OvWindowing::Dialogs::MessageBox::EButtonLayout::OK
);
}
if (!std::filesystem::exists(m_projectScriptFolder))
{
std::filesystem::create_directories(m_projectScriptFolder);
OvWindowing::Dialogs::MessageBox message
(
"Scripts folder not found",
"The \"Scripts/\" folders hasn't been found in your project directory.\nIt has been automatically generated",
OvWindowing::Dialogs::MessageBox::EMessageType::WARNING,
OvWindowing::Dialogs::MessageBox::EButtonLayout::OK
);
}
auto& refreshButton = CreateWidget<Buttons::Button>("Rescan assets");
refreshButton.ClickedEvent += std::bind(&AssetBrowser::Refresh, this);
refreshButton.lineBreak = false;
refreshButton.idleBackgroundColor = { 0.f, 0.5f, 0.0f };
auto& importButton = CreateWidget<Buttons::Button>("Import asset");
importButton.ClickedEvent += EDITOR_BIND(ImportAsset, m_projectAssetFolder);
importButton.idleBackgroundColor = { 0.7f, 0.5f, 0.0f };
m_assetList = &CreateWidget<Layout::Group>();
Fill();
}
这是AssetBrowser的构造函数,除了普通的直接赋值,还有一些其他操作。如果还没创建项目资源的文件夹或脚本文件夹,那么会创建,同时弹窗提醒。接下来会创建刷新和导入的按钮,并绑定功能。接着创建资源列表,最后执行Fill()
void OvEditor::Panels::AssetBrowser::Fill()
{
m_assetList->CreateWidget<OvUI::Widgets::Visual::Separator>();
ConsiderItem(nullptr, std::filesystem::directory_entry(m_engineAssetFolder), true);
m_assetList->CreateWidget<OvUI::Widgets::Visual::Separator>();
ConsiderItem(nullptr, std::filesystem::directory_entry(m_projectAssetFolder), false);
m_assetList->CreateWidget<OvUI::Widgets::Visual::Separator>();
ConsiderItem(nullptr, std::filesystem::directory_entry(m_projectScriptFolder), false, false, true);
}
Fill函数用于填充资源列表的各个部件。
void OvEditor::Panels::AssetBrowser::Clear()
{
m_assetList->RemoveAllWidgets();
}
void OvEditor::Panels::AssetBrowser::Refresh()
{
Clear();
Fill();
}
两个比较短的函数,一起说了。clear用于清除所有部件,而refresh用于刷新部件。
void OvEditor::Panels::AssetBrowser::ParseFolder(Layout::TreeNode& p_root, const std::filesystem::directory_entry& p_directory, bool p_isEngineItem, bool p_scriptFolder)
{
for (auto& item : std::filesystem::directory_iterator(p_directory))
if (item.is_directory())
ConsiderItem(&p_root, item, p_isEngineItem, false, p_scriptFolder);
for (auto& item : std::filesystem::directory_iterator(p_directory))
if (!item.is_directory())
ConsiderItem(&p_root, item, p_isEngineItem, false, p_scriptFolder);
}
ParseFolder用于解析文件夹,实际上就是显示组件和文件。
void OvEditor::Panels::AssetBrowser::ConsiderItem(OvUI::Widgets::Layout::TreeNode* p_root, const std::filesystem::directory_entry& p_entry, bool p_isEngineItem, bool p_autoOpen, bool p_scriptFolder)
{
bool isDirectory = p_entry.is_directory();
std::string itemname = OvTools::Utils::PathParser::GetElementName(p_entry.path().string());
std::string path = p_entry.path().string();
if (isDirectory && path.back() != '\\')
path += '\\';
std::string resourceFormatPath = EDITOR_EXEC(GetResourcePath(path, p_isEngineItem));
bool protectedItem = !p_root || p_isEngineItem;
OvTools::Utils::PathParser::EFileType fileType = OvTools::Utils::PathParser::GetFileType(itemname);
if (fileType == OvTools::Utils::PathParser::EFileType::UNKNOWN && !isDirectory)
{
return;
}
auto& itemGroup = p_root ? p_root->CreateWidget<Layout::Group>() : m_assetList->CreateWidget<Layout::Group>();
uint32_t iconTextureID = isDirectory ? EDITOR_CONTEXT(editorResources)->GetTexture("Icon_Folder")->id : EDITOR_CONTEXT(editorResources)->GetFileIcon(itemname)->id;
itemGroup.CreateWidget<Visual::Image>(iconTextureID, OvMaths::FVector2{ 16, 16 }).lineBreak = false;
if (isDirectory)
{
auto& treeNode = itemGroup.CreateWidget<Layout::TreeNode>(itemname);
if (p_autoOpen)
treeNode.Open();
auto& ddSource = treeNode.AddPlugin<OvUI::Plugins::DDSource<std::pair<std::string, Layout::Group*>>>("Folder", resourceFormatPath, std::make_pair(resourceFormatPath, &itemGroup));
if (!p_root || p_scriptFolder)
treeNode.RemoveAllPlugins();
auto& contextMenu = !p_scriptFolder ? treeNode.AddPlugin<FolderContextualMenu>(path, protectedItem && resourceFormatPath != "") : treeNode.AddPlugin<ScriptFolderContextualMenu>(path, protectedItem && resourceFormatPath != "");
contextMenu.userData = static_cast<void*>(&treeNode);
contextMenu.ItemAddedEvent += [this, &treeNode, path, p_isEngineItem, p_scriptFolder] (std::string p_string)
{
treeNode.Open();
treeNode.RemoveAllWidgets();
ParseFolder(treeNode, std::filesystem::directory_entry(OvTools::Utils::PathParser::GetContainingFolder(p_string)), p_isEngineItem, p_scriptFolder);
};
if (!p_scriptFolder)
{
if (!p_isEngineItem)
{
treeNode.AddPlugin<OvUI::Plugins::DDTarget<std::pair<std::string, Layout::Group*>>>("Folder").DataReceivedEvent += [this, &treeNode, path, p_isEngineItem](std::pair<std::string, Layout::Group*> p_data)
{
if (!p_data.first.empty())
{
std::string folderReceivedPath = EDITOR_EXEC(GetRealPath(p_data.first));
std::string folderName = OvTools::Utils::PathParser::GetElementName(folderReceivedPath) + '\\';
std::string prevPath = folderReceivedPath;
std::string correctPath = m_pathUpdate.find(&treeNode) != m_pathUpdate.end() ? m_pathUpdate.at(&treeNode) : path;
std::string newPath = correctPath + folderName;
if (!(newPath.find(prevPath) != std::string::npos) || prevPath == newPath)
{
if (!std::filesystem::exists(newPath))
{
bool isEngineFolder = p_data.first.at(0) == ':';
if (isEngineFolder)
std::filesystem::copy(prevPath, newPath, std::filesystem::copy_options::recursive);
else
{
RenameAsset(prevPath, newPath);
EDITOR_EXEC(PropagateFolderRename(prevPath, newPath));
}
treeNode.Open();
treeNode.RemoveAllWidgets();
ParseFolder(treeNode, std::filesystem::directory_entry(correctPath), p_isEngineItem);
if (!isEngineFolder)
p_data.second->Destroy();
}
else if (prevPath == newPath)
{
}
else
{
using namespace OvWindowing::Dialogs;
MessageBox errorMessage("Folder already exists", "You can't move this folder to this location because the name is already taken", MessageBox::EMessageType::ERROR, MessageBox::EButtonLayout::OK);
}
}
else
{
using namespace OvWindowing::Dialogs;
MessageBox errorMessage("Wow!", "Crazy boy!", MessageBox::EMessageType::ERROR, MessageBox::EButtonLayout::OK);
}
}
};
treeNode.AddPlugin<OvUI::Plugins::DDTarget<std::pair<std::string, Layout::Group*>>>("File").DataReceivedEvent += [this, &treeNode, path, p_isEngineItem](std::pair<std::string, Layout::Group*> p_data)
{
if (!p_data.first.empty())
{
std::string fileReceivedPath = EDITOR_EXEC(GetRealPath(p_data.first));
std::string fileName = OvTools::Utils::PathParser::GetElementName(fileReceivedPath);
std::string prevPath = fileReceivedPath;
std::string correctPath = m_pathUpdate.find(&treeNode) != m_pathUpdate.end() ? m_pathUpdate.at(&treeNode) : path;
std::string newPath = correctPath + fileName;
if (!std::filesystem::exists(newPath))
{
bool isEngineFile = p_data.first.at(0) == ':';
if (isEngineFile)
std::filesystem::copy_file(prevPath, newPath);
else
{
RenameAsset(prevPath, newPath);
EDITOR_EXEC(PropagateFileRename(prevPath, newPath));
}
treeNode.Open();
treeNode.RemoveAllWidgets();
ParseFolder(treeNode, std::filesystem::directory_entry(correctPath), p_isEngineItem);
if (!isEngineFile)
p_data.second->Destroy();
}
else if (prevPath == newPath)
{
}
else
{
using namespace OvWindowing::Dialogs;
MessageBox errorMessage("File already exists", "You can't move this file to this location because the name is already taken", MessageBox::EMessageType::ERROR, MessageBox::EButtonLayout::OK);
}
}
};
}
contextMenu.DestroyedEvent += [&itemGroup](std::string p_deletedPath) { itemGroup.Destroy(); };
contextMenu.RenamedEvent += [this, &treeNode, path, &ddSource, p_isEngineItem](std::string p_prev, std::string p_newPath)
{
p_newPath += '\\';
if (!std::filesystem::exists(p_newPath))
{
RenameAsset(p_prev, p_newPath);
EDITOR_EXEC(PropagateFolderRename(p_prev, p_newPath));
std::string elementName = OvTools::Utils::PathParser::GetElementName(p_newPath);
std::string data = OvTools::Utils::PathParser::GetContainingFolder(ddSource.data.first) + elementName + "\\";
ddSource.data.first = data;
ddSource.tooltip = data;
treeNode.name = elementName;
treeNode.Open();
treeNode.RemoveAllWidgets();
ParseFolder(treeNode, std::filesystem::directory_entry(p_newPath), p_isEngineItem);
m_pathUpdate[&treeNode] = p_newPath;
}
else
{
using namespace OvWindowing::Dialogs;
MessageBox errorMessage("Folder already exists", "You can't rename this folder because the given name is already taken", MessageBox::EMessageType::ERROR, MessageBox::EButtonLayout::OK);
}
};
contextMenu.ItemAddedEvent += [this, &treeNode, p_isEngineItem](std::string p_path)
{
treeNode.RemoveAllWidgets();
ParseFolder(treeNode, std::filesystem::directory_entry(OvTools::Utils::PathParser::GetContainingFolder(p_path)), p_isEngineItem);
};
}
contextMenu.CreateList();
treeNode.OpenedEvent += [this, &treeNode, path, p_isEngineItem, p_scriptFolder]
{
treeNode.RemoveAllWidgets();
std::string updatedPath = OvTools::Utils::PathParser::GetContainingFolder(path) + treeNode.name;
ParseFolder(treeNode, std::filesystem::directory_entry(updatedPath), p_isEngineItem, p_scriptFolder);
};
treeNode.ClosedEvent += [this, &treeNode]
{
treeNode.RemoveAllWidgets();
};
}
else
{
auto& clickableText = itemGroup.CreateWidget<Texts::TextClickable>(itemname);
FileContextualMenu* contextMenu = nullptr;
switch (fileType)
{
case OvTools::Utils::PathParser::EFileType::MODEL: contextMenu = &clickableText.AddPlugin<ModelContextualMenu>(path, protectedItem); break;
case OvTools::Utils::PathParser::EFileType::TEXTURE: contextMenu = &clickableText.AddPlugin<TextureContextualMenu>(path, protectedItem); break;
case OvTools::Utils::PathParser::EFileType::SHADER: contextMenu = &clickableText.AddPlugin<ShaderContextualMenu>(path, protectedItem); break;
case OvTools::Utils::PathParser::EFileType::MATERIAL: contextMenu = &clickableText.AddPlugin<MaterialContextualMenu>(path, protectedItem); break;
case OvTools::Utils::PathParser::EFileType::SCENE: contextMenu = &clickableText.AddPlugin<SceneContextualMenu>(path, protectedItem); break;
default: contextMenu = &clickableText.AddPlugin<FileContextualMenu>(path, protectedItem); break;
}
contextMenu->CreateList();
contextMenu->DestroyedEvent += [&itemGroup](std::string p_deletedPath)
{
itemGroup.Destroy();
if (EDITOR_CONTEXT(sceneManager).GetCurrentSceneSourcePath() == p_deletedPath)
EDITOR_CONTEXT(sceneManager).ForgetCurrentSceneSourcePath();
};
auto& ddSource = clickableText.AddPlugin<OvUI::Plugins::DDSource<std::pair<std::string, Layout::Group*>>>
(
"File",
resourceFormatPath,
std::make_pair(resourceFormatPath, &itemGroup)
);
contextMenu->RenamedEvent += [&ddSource, &clickableText, p_scriptFolder](std::string p_prev, std::string p_newPath)
{
if (p_newPath != p_prev)
{
if (!std::filesystem::exists(p_newPath))
{
RenameAsset(p_prev, p_newPath);
std::string elementName = OvTools::Utils::PathParser::GetElementName(p_newPath);
ddSource.data.first = OvTools::Utils::PathParser::GetContainingFolder(ddSource.data.first) + elementName;
if (!p_scriptFolder)
{
EDITOR_EXEC(PropagateFileRename(p_prev, p_newPath));
if (EDITOR_CONTEXT(sceneManager).GetCurrentSceneSourcePath() == p_prev)
EDITOR_CONTEXT(sceneManager).StoreCurrentSceneSourcePath(p_newPath);
}
else
{
EDITOR_EXEC(PropagateScriptRename(p_prev, p_newPath));
}
clickableText.content = elementName;
}
else
{
using namespace OvWindowing::Dialogs;
MessageBox errorMessage("File already exists", "You can't rename this file because the given name is already taken", MessageBox::EMessageType::ERROR, MessageBox::EButtonLayout::OK);
}
}
};
contextMenu->DuplicateEvent += [this, &clickableText, p_root, path, p_isEngineItem] (std::string newItem)
{
EDITOR_EXEC(DelayAction(std::bind(&AssetBrowser::ConsiderItem, this, p_root, std::filesystem::directory_entry{ newItem }, p_isEngineItem, false, false), 0));
};
if (fileType == OvTools::Utils::PathParser::EFileType::TEXTURE)
{
auto& texturePreview = clickableText.AddPlugin<TexturePreview>();
texturePreview.SetPath(resourceFormatPath);
}
}
}
ConsiderItem这个函数比较长。给定以树的结构组织的资源,首先解析出文件名、路径和类型,对未知的文件直接跳过。如果给定根节点就以根节点再建树,不然保持原结构。然后找到物件对应的图片。如果给定节点是字典,会进行增删改查的操作,不然则视为可点击文本,并执行一系列操作。
到此结束AssetBrowser。
总结
好像有些太多了,决定还是分开来,这次就先说AssetBrowser相关的内容,其余的分到接下来的文章中讨论。
|