From dc09ba073744292a4d4af0445e5095f424fffa22 Mon Sep 17 00:00:00 2001 From: rtk0c Date: Wed, 9 Jun 2021 22:18:32 -0700 Subject: Refactor asset management UI into AssetList --- core/src/Model/Assets.cpp | 187 +++++++++++++++++++++++++++--- core/src/Model/Assets.hpp | 41 +++++-- core/src/Model/Template/Template.hpp | 12 +- core/src/Model/Template/Template_Main.cpp | 96 +++++++++++++-- core/src/Model/Workflow/Workflow.hpp | 11 +- core/src/Model/Workflow/Workflow_Main.cpp | 74 +++++++++++- core/src/Model/fwd.hpp | 1 + core/src/UI/UI_Templates.cpp | 168 ++++----------------------- core/src/UI/UI_Workflows.cpp | 107 ++++------------- 9 files changed, 429 insertions(+), 268 deletions(-) (limited to 'core') diff --git a/core/src/Model/Assets.cpp b/core/src/Model/Assets.cpp index a1e9730..0e53537 100644 --- a/core/src/Model/Assets.cpp +++ b/core/src/Model/Assets.cpp @@ -1,7 +1,13 @@ #include "Assets.hpp" +#include "UI/Localization.hpp" +#include "UI/UI.hpp" + #include +#include +#include #include +#include #include using namespace std::literals::string_view_literals; @@ -11,6 +17,63 @@ Asset::Asset() { } +class AssetList::Private +{ +public: + tsl::array_map Assets; + tsl::array_map> 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("Duplicate template name"); + break; + case NameSelectionError::Empty: + ImGui::ErrorMessage("Template name cannot be empty"); + 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() + : mPrivate{ std::make_unique() } +{ +} + +// Write an empty destructor here so std::unique_ptr's destructor can see AssetList::Private's implementation +AssetList::~AssetList() +{ +} + void AssetList::Reload() { DiscoverFiles([this](SavedAsset asset) -> void { @@ -20,8 +83,8 @@ void AssetList::Reload() const SavedAsset* AssetList::FindByName(std::string_view name) const { - auto iter = mAssets.find(name); - if (iter != mAssets.end()) { + auto iter = mPrivate->Assets.find(name); + if (iter != mPrivate->Assets.end()) { return &iter.value(); } else { return nullptr; @@ -30,7 +93,7 @@ const SavedAsset* AssetList::FindByName(std::string_view name) const const SavedAsset& AssetList::Create(SavedAsset asset) { - auto [iter, DISCARD] = mAssets.insert(asset.Name, SavedAsset{}); + auto [iter, DISCARD] = mPrivate->Assets.insert(asset.Name, SavedAsset{}); auto& savedAsset = iter.value(); savedAsset = std::move(asset); @@ -67,57 +130,85 @@ std::unique_ptr AssetList::Load(const SavedAsset& asset) const const SavedAsset* AssetList::Rename(std::string_view oldName, std::string_view newName) { - auto iter = mAssets.find(oldName); - if (iter == mAssets.end()) return nullptr; + auto iter = mPrivate->Assets.find(oldName); + if (iter == mPrivate->Assets.end()) return nullptr; auto info = std::move(iter.value()); info.Name = newName; - auto [newIter, DISCARD] = mAssets.insert(newName, std::move(info)); - mAssets.erase(iter); + auto [newIter, DISCARD] = mPrivate->Assets.insert(newName, std::move(info)); + mPrivate->Assets.erase(iter); return &newIter.value(); } bool AssetList::Remove(std::string_view name) { - auto iter = mAssets.find(name); - if (iter == mAssets.end()) { + auto iter = mPrivate->Assets.find(name); + if (iter == mPrivate->Assets.end()) { return false; } auto& asset = iter.value(); fs::remove(RetrievePathFromAsset(asset)); - mAssets.erase(iter); + mPrivate->Assets.erase(iter); return true; } int AssetList::GetCacheSizeLimit() const { - return mCacheSizeLimit; + return mPrivate->CacheSizeLimit; } void AssetList::SetCacheSizeLimit(int limit) { - mCacheSizeLimit = limit; + mPrivate->CacheSizeLimit = limit; } -void AssetList::DrawBigIcons(ListState& state) +void AssetList::DisplayBigIconsList(ListState& state) { // TODO } -void AssetList::DrawDetails(ListState& state) +void AssetList::DisplayDetailsTable(ListState& state) { SetupDetailsTable("AssetDetailsTable"); - for (auto& asset : mAssets) { - DrawDetailsTableRow(asset); + for (auto& asset : mPrivate->Assets) { + DrawDetailsTableRow(state, asset); ImGui::TableNextRow(); } ImGui::EndTable(); } +void AssetList::OpenBigIconsPopup() +{ + ImGui::OpenPopup("Manage assets##BigIcons"); +} + +void AssetList::DisplayBigIconsPopup(PopupState& state) +{ + if (ImGui::BeginPopupModal("Manage assets##BigIcons")) { + DisplayBigIconsList(state); + DisplayPopupControls(state); + ImGui::EndPopup(); + } +} + +void AssetList::OpenDetailsPopup() +{ + ImGui::OpenPopup("Manage assets##Details"); +} + +void AssetList::DisplayDetailsPopup(PopupState& state) +{ + if (ImGui::BeginPopupModal("Manage assets##Details")) { + DisplayBigIconsList(state); + DisplayPopupControls(state); + ImGui::EndPopup(); + } +} + void AssetList::DiscoverFilesByExtension(const std::function& callback, const fs::path& containerDir, std::string_view extension) const { for (auto entry : fs::directory_iterator(containerDir)) { @@ -142,3 +233,67 @@ void AssetList::DiscoverFilesByHeader(const std::function& cal { // TODO } + +void AssetList::DisplayPopupControls(PopupState& state) +{ + auto& ps = mPrivate->PopupPrivateState; + auto ls = LocaleStrings::Instance.get(); + + bool openedDummy = false; + + if (ImGui::Button(ls->Add.Get())) { + ImGui::OpenPopup("Create template"); + } + if (ImGui::BeginPopupModal("Create template", &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { + DisplayAssetCreator(state); + ImGui::EndPopup(); + } + + ImGui::SameLine(); + if (ImGui::Button(ls->Rename.Get(), state.SelectedAsset == nullptr)) { + ImGui::OpenPopup("Rename template"); + } + if (ImGui::BeginPopupModal("Rename template")) { + if (ImGui::InputText("New name", &ps.NewName)) { + ps.Validate(*this); + } + + if (ImGui::Button(ls->DialogConfirm.Get(), 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(ls->DialogCancel.Get())) { + ImGui::CloseCurrentPopup(); + } + + ps.ShowErrors(); + + ImGui::EndPopup(); + } + + ImGui::SameLine(); + if (ImGui::Button(ls->Delete.Get(), state.SelectedAsset == nullptr)) { + ImGui::OpenPopup("Delete confirmation"); + } + if (ImGui::BeginPopupModal("Delete confirmation")) { + if (ImGui::Button(ls->DialogConfirm.Get())) { + ImGui::CloseCurrentPopup(); + + auto& assetName = state.SelectedAsset->Name; + Remove(assetName); + + state.SelectedAsset = nullptr; + } + ImGui::SameLine(); + if (ImGui::Button(ls->DialogCancel.Get())) { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } +} diff --git a/core/src/Model/Assets.hpp b/core/src/Model/Assets.hpp index feb4432..e035f51 100644 --- a/core/src/Model/Assets.hpp +++ b/core/src/Model/Assets.hpp @@ -1,8 +1,9 @@ -#pragma once +#pragma once #include "Utils/UUID.hpp" -#include +#include "Assets.hpp" + #include #include #include @@ -27,15 +28,22 @@ public: virtual ~Asset() = default; }; +enum class NameSelectionError +{ + None, + Duplicated, + Empty, +}; + class AssetList { private: - tsl::array_map mAssets; - tsl::array_map> mCache; - int mCacheSizeLimit = 0; + class Private; + std::unique_ptr mPrivate; public: - virtual ~AssetList() = default; + AssetList(); + virtual ~AssetList(); // TODO support file watches void Reload(); @@ -58,8 +66,16 @@ public: { const SavedAsset* SelectedAsset = nullptr; }; - void DrawBigIcons(ListState& state); - void DrawDetails(ListState& state); + void DisplayBigIconsList(ListState& state); + void DisplayDetailsTable(ListState& state); + + struct PopupState : public ListState + { + }; + void OpenBigIconsPopup(); + void DisplayBigIconsPopup(PopupState& state); + void OpenDetailsPopup(); + void DisplayDetailsPopup(PopupState& state); protected: virtual void DiscoverFiles(const std::function& callback) const = 0; @@ -77,10 +93,15 @@ protected: virtual uuids::uuid RetrieveUuidFromFile(const std::filesystem::path& file) const = 0; virtual std::filesystem::path RetrievePathFromAsset(const SavedAsset& asset) const = 0; + virtual void DisplayAssetCreator(PopupState& state) = 0; + /// This should call ImGui::BeginTable() along with other accessories such as setting up the header row. virtual void SetupDetailsTable(const char* tableId) const = 0; - virtual void DrawBigIcon(const SavedAsset& asset) const = 0; - virtual void DrawDetailsTableRow(const SavedAsset& asset) const = 0; + virtual void DrawBigIcon(ListState& state, const SavedAsset& asset) const = 0; + virtual void DrawDetailsTableRow(ListState& state, const SavedAsset& asset) const = 0; + +private: + void DisplayPopupControls(PopupState& state); }; template diff --git a/core/src/Model/Template/Template.hpp b/core/src/Model/Template/Template.hpp index 600bb26..fdac574 100644 --- a/core/src/Model/Template/Template.hpp +++ b/core/src/Model/Template/Template.hpp @@ -46,6 +46,12 @@ public: class TemplateAssetList final : public AssetListTyped