diff options
Diffstat (limited to 'app/source/Cplt/Model/Assets.cpp')
-rw-r--r-- | app/source/Cplt/Model/Assets.cpp | 306 |
1 files changed, 306 insertions, 0 deletions
diff --git a/app/source/Cplt/Model/Assets.cpp b/app/source/Cplt/Model/Assets.cpp new file mode 100644 index 0000000..0dfe847 --- /dev/null +++ b/app/source/Cplt/Model/Assets.cpp @@ -0,0 +1,306 @@ +#include "Assets.hpp" + +#include <Cplt/UI/UI.hpp> +#include <Cplt/Utils/I18n.hpp> +#include <Cplt/Utils/IO/DataStream.hpp> +#include <Cplt/Utils/IO/StringIntegration.hpp> +#include <Cplt/Utils/IO/UuidIntegration.hpp> + +#include <IconsFontAwesome.h> +#include <imgui.h> +#include <imgui_stdlib.h> +#include <tsl/array_map.h> +#include <string> +#include <utility> + +using namespace std::literals::string_view_literals; +namespace fs = std::filesystem; + +template <class TSavedAsset, class TStream> +void OperateStreamForSavedAsset(TSavedAsset& cell, TStream& proxy) +{ + proxy.template ObjectAdapted<DataStreamAdapters::String>(cell.Name); + proxy.template ObjectAdapted<DataStreamAdapters::Uuid>(cell.Uuid); + proxy.Value(cell.Payload); +} + +void SavedAsset::ReadFromDataStream(InputDataStream& stream) +{ + ::OperateStreamForSavedAsset(*this, stream); +} + +void SavedAsset::WriteToDataStream(OutputDataStream& stream) const +{ + ::OperateStreamForSavedAsset(*this, stream); +} + +Asset::Asset() = default; + +class AssetList::Private +{ +public: + Project* ConnectedProject; + tsl::array_map<char, SavedAsset> Assets; + tsl::array_map<char, std::unique_ptr<Asset>> Cache; + int CacheSizeLimit = 0; + + struct + { + std::string NewName; + NameSelectionError NewNameError = NameSelectionError::Empty; + + void ShowErrors() const + { + switch (NewNameError) { + case NameSelectionError::None: break; + case NameSelectionError::Duplicated: + ImGui::ErrorMessage(I18N_TEXT("Duplicate name", L10N_DUPLICATE_NAME_ERROR)); + break; + case NameSelectionError::Empty: + ImGui::ErrorMessage(I18N_TEXT("Name cannot be empty", L10N_EMPTY_NAME_ERROR)); + break; + } + } + + bool HasErrors() const + { + return NewNameError != NameSelectionError::None; + } + + void Validate(const AssetList& self) + { + if (NewName.empty()) { + NewNameError = NameSelectionError::Empty; + return; + } + + if (self.FindByName(NewName)) { + NewNameError = NameSelectionError::Duplicated; + return; + } + + NewNameError = NameSelectionError::None; + } + } PopupPrivateState; +}; + +AssetList::AssetList(Project& project) + : mPrivate{ std::make_unique<Private>() } +{ + mPrivate->ConnectedProject = &project; +} + +// Write an empty destructor here so std::unique_ptr's destructor can see AssetList::Private's implementation +AssetList::~AssetList() +{ +} + +Project& AssetList::GetConnectedProject() const +{ + return *mPrivate->ConnectedProject; +} + +void AssetList::Reload() +{ + // TODO fix asset dicovery loading + mPrivate->Assets.clear(); + mPrivate->Cache.clear(); + DiscoverFiles([this](SavedAsset asset) -> void { + mPrivate->Assets.insert(asset.Name, std::move(asset)); + }); +} + +int AssetList::GetCount() const +{ + return mPrivate->Assets.size(); +} + +const tsl::array_map<char, SavedAsset>& AssetList::GetAssets() const +{ + return mPrivate->Assets; +} + +const SavedAsset* AssetList::FindByName(std::string_view name) const +{ + auto iter = mPrivate->Assets.find(name); + if (iter != mPrivate->Assets.end()) { + return &iter.value(); + } else { + return nullptr; + } +} + +const SavedAsset& AssetList::Create(SavedAsset asset) +{ + auto [iter, DISCARD] = mPrivate->Assets.insert(asset.Name, SavedAsset{}); + auto& savedAsset = iter.value(); + + savedAsset = std::move(asset); + if (savedAsset.Uuid.is_nil()) { + savedAsset.Uuid = uuids::uuid_random_generator{}(); + } + + SaveInstance(savedAsset, nullptr); + + return savedAsset; +} + +std::unique_ptr<Asset> AssetList::CreateAndLoad(SavedAsset assetIn) +{ + auto& savedAsset = Create(std::move(assetIn)); + auto asset = std::unique_ptr<Asset>(CreateInstance(savedAsset)); + return asset; +} + +std::unique_ptr<Asset> AssetList::Load(std::string_view name) const +{ + if (auto savedAsset = FindByName(name)) { + auto asset = Load(*savedAsset); + return asset; + } else { + return nullptr; + } +} + +std::unique_ptr<Asset> AssetList::Load(const SavedAsset& asset) const +{ + return std::unique_ptr<Asset>(LoadInstance(asset)); +} + +const SavedAsset* AssetList::Rename(std::string_view oldName, std::string_view newName) +{ + auto iter = mPrivate->Assets.find(oldName); + if (iter == mPrivate->Assets.end()) return nullptr; + + auto info = std::move(iter.value()); + info.Name = newName; + + RenameInstanceOnDisk(info, oldName); + + mPrivate->Assets.erase(iter); + auto [newIter, DISCARD] = mPrivate->Assets.insert(newName, std::move(info)); + + return &newIter.value(); +} + +bool AssetList::Remove(std::string_view name) +{ + auto iter = mPrivate->Assets.find(name); + if (iter == mPrivate->Assets.end()) { + return false; + } + auto& asset = iter.value(); + + fs::remove(RetrievePathFromAsset(asset)); + mPrivate->Assets.erase(iter); + + return true; +} + +int AssetList::GetCacheSizeLimit() const +{ + return mPrivate->CacheSizeLimit; +} + +void AssetList::SetCacheSizeLimit(int limit) +{ + mPrivate->CacheSizeLimit = limit; +} + +void AssetList::DisplayIconsList(ListState& state) +{ + // TODO +} + +void AssetList::DisplayDetailsList(ListState& state) +{ + // Note: stub function remained here in case any state processing needs to be done before issuing to implementers + DisplayDetailsTable(state); +} + +void AssetList::DisplayControls(ListState& state) +{ + auto& ps = mPrivate->PopupPrivateState; + bool openedDummy = true; + + if (ImGui::Button(ICON_FA_PLUS " " I18N_TEXT("Add", L10N_ADD))) { + ImGui::OpenPopup(I18N_TEXT("Add asset wizard", L10N_ADD_ASSET_DIALOG_TITLE)); + } + if (ImGui::BeginPopupModal(I18N_TEXT("Add asset wizard", L10N_ADD_ASSET_DIALOG_TITLE), &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { + DisplayAssetCreator(state); + ImGui::EndPopup(); + } + + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_I_CURSOR " " I18N_TEXT("Rename", L10N_RENAME), state.SelectedAsset == nullptr)) { + ImGui::OpenPopup(I18N_TEXT("Rename asset wizard", L10N_RENAME_ASSET_DIALOG_TITLE)); + } + if (ImGui::BeginPopupModal(I18N_TEXT("Rename asset wizard", L10N_RENAME_ASSET_DIALOG_TITLE), &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { + if (ImGui::InputText(I18N_TEXT("Name", L10N_NAME), &ps.NewName)) { + ps.Validate(*this); + } + + if (ImGui::Button(I18N_TEXT("Confirm", L10N_CONFIRM), ps.HasErrors())) { + ImGui::CloseCurrentPopup(); + + auto movedAsset = Rename(state.SelectedAsset->Name, ps.NewName); + // Update the selected pointer to the new location (we mutated the map, the pointer may be invalid now) + state.SelectedAsset = movedAsset; + + ps = {}; + } + ImGui::SameLine(); + if (ImGui::Button(I18N_TEXT("Cancel", L10N_CANCEL))) { + ImGui::CloseCurrentPopup(); + } + + ps.ShowErrors(); + + ImGui::EndPopup(); + } + + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_TRASH " " I18N_TEXT("Delete", L10N_DELETE), state.SelectedAsset == nullptr)) { + ImGui::OpenPopup(I18N_TEXT("Delete asset", L10N_DELETE_ASSET_DIALOG_TITLE)); + } + if (ImGui::BeginPopupModal(I18N_TEXT("Delete asset", L10N_DELETE_ASSET_DIALOG_TITLE), &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { + if (ImGui::Button(I18N_TEXT("Confirm", L10N_CONFIRM))) { + ImGui::CloseCurrentPopup(); + + auto& assetName = state.SelectedAsset->Name; + Remove(assetName); + + state.SelectedAsset = nullptr; + } + ImGui::SameLine(); + if (ImGui::Button(I18N_TEXT("Cancel", L10N_CANCEL))) { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } +} + +void AssetList::DiscoverFilesByExtension(const std::function<void(SavedAsset)>& callback, const fs::path& containerDir, std::string_view extension) const +{ + for (auto entry : fs::directory_iterator(containerDir)) { + if (!entry.is_regular_file()) continue; + + // If the caller provided an extension to match against, and it doesn't equal to current file extension, skip + if (!extension.empty() && + entry.path().extension() != extension) + { + continue; + } + + callback(SavedAsset{ + .Name = RetrieveNameFromFile(entry.path()), + .Uuid = RetrieveUuidFromFile(entry.path()), + // TODO load payload + }); + } +} + +void AssetList::DiscoverFilesByHeader(const std::function<void(SavedAsset)>& callback, const fs::path& containerDir, const std::function<bool(std::istream&)>& validater) const +{ + // TODO +} |