Files
WickedEngine/Editor/ContentBrowserWindow.cpp
T
2026-01-23 14:25:50 +01:00

550 lines
16 KiB
C++

#include "stdafx.h"
#include "ContentBrowserWindow.h"
using namespace wi::ecs;
using namespace wi::scene;
void ContentBrowserWindow::Create(EditorComponent* _editor)
{
editor = _editor;
control_size = 30;
wi::gui::Window::Create("Content Browser");
RemoveWidget(&scrollbar_horizontal);
sortingComboBox.Create("");
sortingComboBox.AddItem("By recent", SORTING_RECENT);
sortingComboBox.AddItem("By name", SORTING_NAME);
sortingComboBox.AddItem("By path", SORTING_PATH);
sortingComboBox.AddItem("By size", SORTING_SIZE);
sortingComboBox.SetTooltip("Choose how items are sorted in the content browser.\nBy recent: Most recently used items first\nBy name: Alphabetically by file name\nBy path: Alphabetically by full path\nBy size: Largest files first");
sortingComboBox.SetLocalizationEnabled(false);
if (editor->main->config.GetSection("options").Has("content_browser_sorting"))
{
current_sorting = (SORTING)editor->main->config.GetSection("options").GetInt("content_browser_sorting");
}
sortingComboBox.SetSelected((int)current_sorting);
sortingComboBox.OnSelect([this](wi::gui::EventArgs args) {
current_sorting = (SORTING)args.iValue;
editor->main->config.GetSection("options").Set("content_browser_sorting", args.iValue);
editor->main->config.Commit();
if (current_selection != SELECTION_COUNT)
{
SetSelection(current_selection);
}
});
AddWidget(&sortingComboBox);
SetVisible(false);
}
static constexpr float separator = 140; // width of the left selection folder buttons and sorting combobox
void ContentBrowserWindow::Render(const wi::Canvas& canvas, wi::graphics::CommandList cmd) const
{
wi::gui::Window::Render(canvas, cmd);
if (IsVisible() && !IsCollapsed())
{
ApplyScissor(canvas, scissorRect, cmd);
wi::image::Params params;
params.pos = XMFLOAT3(translation.x + separator, translation.y + control_size, 0);
params.siz = XMFLOAT2(2, scale.y - control_size);
params.color = shadow_color;
wi::image::Draw(nullptr, params, cmd);
}
}
void ContentBrowserWindow::Update(const wi::Canvas& canvas, float dt)
{
wi::gui::Window::Update(canvas, dt);
SetShadowRadius(6);
const bool gui_round_enabled = !editor->generalWnd.disableRoundCornersCheckBox.GetCheck();
constexpr float radius = 15;
for (int i = 0; i < arraysize(wi::gui::Widget::sprites); ++i)
{
if (gui_round_enabled)
{
sprites[i].params.enableCornerRounding();
sprites[i].params.corners_rounding[0].radius = radius;
sprites[i].params.corners_rounding[1].radius = radius;
sprites[i].params.corners_rounding[2].radius = radius;
sprites[i].params.corners_rounding[3].radius = radius;
openFolderButton.sprites[i].params.enableCornerRounding();
openFolderButton.sprites[i].params.corners_rounding[0].radius = radius;
openFolderButton.sprites[i].params.corners_rounding[1].radius = radius;
openFolderButton.sprites[i].params.corners_rounding[2].radius = radius;
openFolderButton.sprites[i].params.corners_rounding[3].radius = radius;
}
else
{
sprites[i].params.disableCornerRounding();
openFolderButton.sprites[i].params.disableCornerRounding();
}
}
for (auto& x : folderButtons)
{
x.font.params.h_align = wi::font::WIFALIGN_LEFT;
for (auto& y : x.sprites)
{
if (gui_round_enabled)
{
y.params.enableCornerRounding();
y.params.corners_rounding[3].radius = radius;
}
else
{
y.params.disableCornerRounding();
}
}
x.SetShadowRadius(0);
}
for (auto& x : itemButtons)
{
if (x.sprites[wi::gui::IDLE].textureResource.IsValid())
{
x.sprites[wi::gui::IDLE].params.color = wi::Color::White();
}
for (auto& y : x.sprites)
{
if (gui_round_enabled)
{
y.params.enableCornerRounding();
y.params.corners_rounding[0].radius = radius;
y.params.corners_rounding[1].radius = radius;
y.params.corners_rounding[2].radius = radius;
y.params.corners_rounding[3].radius = radius;
}
else
{
y.params.disableCornerRounding();
}
}
x.SetShadowRadius(4);
}
openFolderButton.SetShadowRadius(0);
const XMFLOAT4 color_on = sprites[wi::gui::FOCUS].params.color;
const XMFLOAT4 color_off = sprites[wi::gui::IDLE].params.color;
for (auto& x : folderButtons)
{
x.sprites[wi::gui::IDLE].params.color = color_off;
}
if (current_selection < SELECTION_COUNT)
{
folderButtons[current_selection].sprites[wi::gui::IDLE].params.color = color_on;
}
}
void ContentBrowserWindow::ResizeLayout()
{
wi::gui::Window::ResizeLayout();
constexpr float padding = 4;
const float width = GetWidgetAreaSize().x;
float y = padding;
sortingComboBox.Detach();
sortingComboBox.SetPos(XMFLOAT2(translation.x + separator + 10, translation.y + control_size + padding));
sortingComboBox.SetSize(XMFLOAT2(separator, 25));
sortingComboBox.AttachTo(this);
openFolderButton.Detach();
openFolderButton.SetPos(XMFLOAT2(translation.x + padding, translation.y + scale.y - openFolderButton.GetSize().y - padding));
openFolderButton.SetSize(XMFLOAT2(separator - padding * 2, openFolderButton.GetSize().y));
openFolderButton.AttachTo(this);
size_t seli = 0;
for (auto& x : folderButtons)
{
x.Detach();
x.SetPos(XMFLOAT2(translation.x + padding, translation.y + control_size + y));
x.SetSize(XMFLOAT2(separator - padding * 2, x.GetSize().y));
y += x.GetSize().y + padding;
x.AttachTo(this);
seli++;
if (seli == SELECTION_RECENTFOLDER_BEGIN)
y += x.GetSize().y + padding;
}
y = padding + 10 + 35;
float sep = separator + 10;
float hoffset = sep;
for (auto& x : itemButtons)
{
x.SetPos(XMFLOAT2(hoffset, y));
hoffset += x.GetSize().x + 15;
if (hoffset + x.GetSize().x >= width)
{
hoffset = sep;
y += x.GetSize().y + 40;
}
}
}
void ContentBrowserWindow::RefreshContent()
{
content_folder = wi::helper::GetCurrentPath() + "/Content/";
wi::helper::MakePathAbsolute(content_folder);
if (!wi::helper::FileExists(content_folder))
{
content_folder = wi::helper::GetCurrentPath() + "/../Content/";
wi::helper::MakePathAbsolute(content_folder);
}
float hei = 25;
float wid = 120;
for (auto& x : folderButtons)
{
RemoveWidget(&x);
}
if (!editor->recentFilenames.empty())
{
wi::gui::Button& button = folderButtons[SELECTION_ALL];
button.Create("All");
button.SetTooltip("List all recently used files, not grouped by folders.");
button.SetLocalizationEnabled(false);
button.SetSize(XMFLOAT2(wid, hei));
button.OnClick([this](wi::gui::EventArgs args) {
SetSelection(SELECTION_ALL);
});
AddWidget(&button, wi::gui::Window::AttachmentOptions::NONE);
if (current_selection == SELECTION_COUNT)
{
current_selection = SELECTION_ALL;
}
}
if (wi::helper::DirectoryExists(content_folder + "models"))
{
wi::gui::Button& button = folderButtons[SELECTION_MODELS];
button.Create("Models");
button.SetTooltip(content_folder + "models");
button.SetLocalizationEnabled(false);
button.SetSize(XMFLOAT2(wid, hei));
button.OnClick([this](wi::gui::EventArgs args) {
SetSelection(SELECTION_MODELS);
});
AddWidget(&button, wi::gui::Window::AttachmentOptions::NONE);
if (current_selection == SELECTION_COUNT)
{
current_selection = SELECTION_MODELS;
}
}
if (wi::helper::DirectoryExists(content_folder + "scripts"))
{
wi::gui::Button& button = folderButtons[SELECTION_SCRIPTS];
button.Create("Scripts");
button.SetTooltip(content_folder + "scripts");
button.SetLocalizationEnabled(false);
button.SetSize(XMFLOAT2(wid, hei));
button.OnClick([this](wi::gui::EventArgs args) {
SetSelection(SELECTION_SCRIPTS);
});
AddWidget(&button, wi::gui::Window::AttachmentOptions::NONE);
if (current_selection == SELECTION_COUNT)
{
current_selection = SELECTION_SCRIPTS;
}
}
for (size_t i = 0; i < editor->recentFolders.size(); ++i)
{
wi::gui::Button& button = folderButtons[SELECTION_RECENTFOLDER_BEGIN + i];
std::string folder = editor->recentFolders[editor->recentFolders.size() - 1 - i];
while (!folder.empty() && (folder.back() == '/' || folder.back() == '\\'))
{
folder.pop_back();
}
const auto last_slash = folder.find_last_of("/\\");
if (last_slash != folder.npos) {
folder = folder.substr(last_slash);
}
button.Create(folder);
button.SetTooltip(editor->recentFolders[editor->recentFolders.size() - 1 - i]); // full folder name!
button.SetLocalizationEnabled(false);
button.SetSize(XMFLOAT2(wid, hei));
button.OnClick([this, i](wi::gui::EventArgs args) {
SetSelection((SELECTION)(SELECTION_RECENTFOLDER_BEGIN + i));
});
AddWidget(&button, wi::gui::Window::AttachmentOptions::NONE);
if (current_selection == SELECTION_COUNT)
{
current_selection = (SELECTION)(SELECTION_RECENTFOLDER_BEGIN + i);
}
}
if (current_selection != SELECTION_COUNT)
{
SetSelection(current_selection);
}
}
void ContentBrowserWindow::SetSelection(SELECTION selection)
{
wi::eventhandler::Subscribe_Once(wi::eventhandler::EVENT_THREAD_SAFE_POINT, [this, selection](uint64_t userdata) {
current_selection = selection;
for (auto& x : itemButtons)
{
RemoveWidget(&x);
}
itemButtons.clear();
added_items.clear();
RemoveWidget(&openFolderButton);
openFolderButton.Create(ICON_OPEN " Go to location");
openFolderButton.SetSize(XMFLOAT2(60, 60));
switch (selection)
{
case SELECTION_SCRIPTS:
AddItems(content_folder + "scripts/", "lua", ICON_SCRIPT);
openFolderButton.OnClick([this](wi::gui::EventArgs args) {
wi::helper::OpenUrl(content_folder + "scripts/");
});
AddWidget(&openFolderButton, wi::gui::Window::AttachmentOptions::NONE);
break;
case SELECTION_MODELS:
AddItems(content_folder + "models/", "wiscene", ICON_OBJECT);
AddItems(content_folder + "models/", "vrma", ICON_ANIMATION);
AddItems(content_folder + "models/", "vrm", ICON_HUMANOID);
AddItems(content_folder + "models/", "gltf", ICON_OBJECT);
AddItems(content_folder + "models/", "glb", ICON_OBJECT);
AddItems(content_folder + "models/", "fbx", ICON_OBJECT);
AddItems(content_folder + "models/", "obj", ICON_OBJECT);
openFolderButton.OnClick([this](wi::gui::EventArgs args) {
wi::helper::OpenUrl(content_folder + "models/");
});
AddWidget(&openFolderButton, wi::gui::Window::AttachmentOptions::NONE);
break;
case SELECTION_ALL:
for (size_t i = 0; i < editor->recentFilenames.size(); ++i)
{
const std::string& fileName = editor->recentFilenames[editor->recentFilenames.size() - 1 - i];
std::string ext = wi::helper::toUpper(wi::helper::GetExtensionFromFileName(fileName));
if (!ext.compare("LUA"))
{
AddItem(fileName, ICON_SCRIPT);
}
else if (!ext.compare("WISCENE"))
{
AddItem(fileName, ICON_OBJECT);
}
else if (!ext.compare("VRMA"))
{
AddItem(fileName, ICON_ANIMATION);
}
else if (!ext.compare("VRM"))
{
AddItem(fileName, ICON_HUMANOID);
}
else if (!ext.compare("GLTF"))
{
AddItem(fileName, ICON_OBJECT);
}
else if (!ext.compare("GLB"))
{
AddItem(fileName, ICON_OBJECT);
}
else if (!ext.compare("FBX"))
{
AddItem(fileName, ICON_OBJECT);
}
else if (!ext.compare("OBJ"))
{
AddItem(fileName, ICON_OBJECT);
}
}
break;
default:
{
size_t i = selection - SELECTION_RECENTFOLDER_BEGIN;
i = std::min(i, editor->recentFolders.size() - 1);
const std::string& folder = editor->recentFolders[editor->recentFolders.size() - 1 - i];
AddItems(folder, "wiscene", ICON_OBJECT);
AddItems(folder, "vrma", ICON_ANIMATION);
AddItems(folder, "vrm", ICON_HUMANOID);
AddItems(folder, "gltf", ICON_OBJECT);
AddItems(folder, "glb", ICON_OBJECT);
AddItems(folder, "fbx", ICON_OBJECT);
AddItems(folder, "obj", ICON_OBJECT);
AddItems(folder, "lua", ICON_SCRIPT);
openFolderButton.OnClick([folder](wi::gui::EventArgs args) {
wi::helper::OpenUrl(folder);
});
AddWidget(&openFolderButton, wi::gui::Window::AttachmentOptions::NONE);
}
break;
}
SortAndCreateItemButtons();
for (auto& x : itemButtons)
{
AddWidget(&x);
}
editor->generalWnd.RefreshTheme();
});
}
void ContentBrowserWindow::AddItems(const std::string& folder, const std::string& extension, const std::string& icon)
{
// Folders parse:
wi::helper::GetFolderNamesInDirectory(folder, [&](const std::string& folderName) {
const std::string itemName = folderName.substr(folder.size()) + "." + extension;
const std::string fileName = folderName + "/" + itemName;
if (wi::helper::FileExists(fileName))
{
AddItem(fileName, icon);
}
});
// Individual items:
wi::helper::GetFileNamesInDirectory(folder, [&](const std::string& fileName) {
AddItem(fileName, icon);
}, extension);
}
void ContentBrowserWindow::AddItem(const std::string& fileName, const std::string& icon)
{
if (!wi::helper::FileExists(fileName))
return;
for (const auto& item : added_items)
{
if (item.fileName == fileName)
return;
}
ItemInfo info;
info.fileName = fileName;
info.icon = icon;
info.file_size = wi::helper::FileSize(fileName);
// Find index in recent filenames list (lower - more recent)
info.recent_index = SIZE_MAX;
for (size_t i = 0; i < editor->recentFilenames.size(); ++i)
{
// Check from the end (most recent) to beginning
const size_t idx = editor->recentFilenames.size() - 1 - i;
if (editor->recentFilenames[idx] == fileName)
{
info.recent_index = i;
break;
}
}
added_items.push_back(info);
}
void ContentBrowserWindow::SortAndCreateItemButtons()
{
switch (current_sorting)
{
case SORTING_RECENT:
std::stable_sort(added_items.begin(), added_items.end(), [](const ItemInfo& a, const ItemInfo& b) {
return a.recent_index < b.recent_index;
});
break;
case SORTING_NAME:
std::sort(added_items.begin(), added_items.end(), [](const ItemInfo& a, const ItemInfo& b) {
return wi::helper::toUpper(wi::helper::GetFileNameFromPath(a.fileName)) < wi::helper::toUpper(wi::helper::GetFileNameFromPath(b.fileName));
});
break;
case SORTING_PATH:
std::sort(added_items.begin(), added_items.end(), [](const ItemInfo& a, const ItemInfo& b) {
return wi::helper::toUpper(a.fileName) < wi::helper::toUpper(b.fileName);
});
break;
case SORTING_SIZE:
std::sort(added_items.begin(), added_items.end(), [](const ItemInfo& a, const ItemInfo& b) {
return a.file_size > b.file_size;
});
break;
default:
break;
}
for (const auto& item : added_items)
{
CreateItemButton(item.fileName, item.icon);
}
}
void ContentBrowserWindow::CreateItemButton(const std::string& fileName, const std::string& icon)
{
static constexpr auto siz = XMFLOAT2(240, 120);
if (!wi::helper::FileExists(fileName))
return;
const std::string itemName = wi::helper::GetFileNameFromPath(fileName);
const std::string folderName = wi::helper::GetDirectoryFromPath(fileName);
const std::string extension = wi::helper::toUpper(wi::helper::GetExtensionFromFileName(fileName));
wi::gui::Button& button = itemButtons.emplace_back();
button.Create(icon);
button.SetSize(siz);
button.SetLocalizationEnabled(false);
button.SetDescription(itemName);
button.OnClick([this, fileName](wi::gui::EventArgs args) {
wi::eventhandler::Subscribe_Once(wi::eventhandler::EVENT_THREAD_SAFE_POINT, [this, fileName](uint64_t userdata) {
editor->Open(fileName);
});
this->SetVisible(false);
});
button.OnRightClick([this, fileName](wi::gui::EventArgs args) {
wi::eventhandler::Subscribe_Once(wi::eventhandler::EVENT_THREAD_SAFE_POINT, [this, fileName](uint64_t userdata) {
editor->NewScene();
editor->Open(fileName);
});
this->SetVisible(false);
});
button.font_description.params.h_align = wi::font::WIFALIGN_CENTER;
button.font_description.params.v_align = wi::font::WIFALIGN_TOP;
button.font.params.size = 42;
button.SetTooltip(fileName + "\nSize: " + wi::helper::GetMemorySizeText(wi::helper::FileSize(fileName)));
if (extension.compare("WISCENE") == 0)
{
wi::Archive::Header archive_header;
wi::graphics::Texture archiveThumbnail = wi::Archive::PeekThumbnail(fileName, &archive_header);
if (archiveThumbnail.IsValid())
{
for (int i = 0; i < arraysize(sprites); ++i)
{
button.sprites[i].textureResource.SetTexture(archiveThumbnail);
}
button.SetText("");
}
button.SetTooltip(button.GetTooltip() + "\nVersion: " + std::to_string(archive_header.version) + (archive_header.properties.bits.compressed ? "\nCompressed : true" : "\nCompressed : false"));
}
else
{
std::string thumbnailName = folderName + "/thumbnail.png";
if (wi::helper::FileExists(thumbnailName))
{
wi::Resource thumbnail = wi::resourcemanager::Load(thumbnailName);
if (thumbnail.IsValid())
{
for (int i = 0; i < arraysize(sprites); ++i)
{
button.sprites[i].textureResource = thumbnail;
}
button.SetText("");
}
}
}
button.SetTooltip(button.GetTooltip() + "\n\nLeft-click: Merge into current scene\nRight-click: Open in new scene tab");
}