aboutsummaryrefslogtreecommitdiff
path: root/app/source/Cplt/Model/Assets.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'app/source/Cplt/Model/Assets.cpp')
-rw-r--r--app/source/Cplt/Model/Assets.cpp306
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
+}