550 lines
16 KiB
C++
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");
|
|
}
|