diff options
Diffstat (limited to 'core/src/Model')
-rw-r--r-- | core/src/Model/Assets.cpp | 187 | ||||
-rw-r--r-- | core/src/Model/Assets.hpp | 41 | ||||
-rw-r--r-- | core/src/Model/Template/Template.hpp | 12 | ||||
-rw-r--r-- | core/src/Model/Template/Template_Main.cpp | 96 | ||||
-rw-r--r-- | core/src/Model/Workflow/Workflow.hpp | 11 | ||||
-rw-r--r-- | core/src/Model/Workflow/Workflow_Main.cpp | 74 | ||||
-rw-r--r-- | core/src/Model/fwd.hpp | 1 |
7 files changed, 379 insertions, 43 deletions
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 <imgui.h> +#include <imgui_stdlib.h> +#include <tsl/array_map.h> #include <fstream> +#include <string> #include <utility> using namespace std::literals::string_view_literals; @@ -11,6 +17,63 @@ Asset::Asset() { } +class AssetList::Private +{ +public: + 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("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<Private>() } +{ +} + +// 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<Asset> 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<void(SavedAsset)>& 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<void(SavedAsset)>& 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 <tsl/array_map.h> +#include "Assets.hpp" + #include <filesystem> #include <iosfwd> #include <memory> @@ -27,15 +28,22 @@ public: virtual ~Asset() = default; }; +enum class NameSelectionError +{ + None, + Duplicated, + Empty, +}; + class AssetList { private: - tsl::array_map<char, SavedAsset> mAssets; - tsl::array_map<char, std::unique_ptr<Asset>> mCache; - int mCacheSizeLimit = 0; + class Private; + std::unique_ptr<Private> 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<void(SavedAsset)>& 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 <class T> 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<Template> { +private: + // AC = Asset Creator + std::string mACNewName; + NameSelectionError mACNewNameError = NameSelectionError::Empty; + Template::Kind mACNewKind = Template::InvalidKind; + protected: virtual void DiscoverFiles(const std::function<void(SavedAsset)>& callback) const override; @@ -58,7 +64,9 @@ protected: virtual Template* LoadImpl(const SavedAsset& diskForm) const override; + virtual void DisplayAssetCreator(PopupState& state) override; + virtual void SetupDetailsTable(const char* tableId) const override; - virtual void DrawBigIcon(const SavedAsset& asset) const override; - virtual void DrawDetailsTableRow(const SavedAsset& asset) const override; + virtual void DrawBigIcon(ListState& state, const SavedAsset& asset) const override; + virtual void DrawDetailsTableRow(ListState& state, const SavedAsset& asset) const override; }; diff --git a/core/src/Model/Template/Template_Main.cpp b/core/src/Model/Template/Template_Main.cpp index 9bebc21..ebec490 100644 --- a/core/src/Model/Template/Template_Main.cpp +++ b/core/src/Model/Template/Template_Main.cpp @@ -2,9 +2,12 @@ #include "Model/GlobalStates.hpp" #include "Model/Project.hpp" +#include "UI/Localization.hpp" +#include "UI/UI.hpp" #include "Utils/UUID.hpp" #include <imgui.h> +#include <imgui_stdlib.h> #include <fstream> using namespace std::literals::string_view_literals; @@ -86,30 +89,107 @@ Template* TemplateAssetList::LoadImpl(const SavedAsset& asset) const return tmpl.release(); } +void TemplateAssetList::DisplayAssetCreator(PopupState& state) +{ + auto ls = LocaleStrings::Instance.get(); + + auto ValidateNewName = [&]() -> void { + if (mACNewName.empty()) { + mACNewNameError = NameSelectionError::Empty; + return; + } + + if (FindByName(mACNewName)) { + mACNewNameError = NameSelectionError::Duplicated; + return; + } + + mACNewNameError = NameSelectionError::None; + }; + + auto ShowNewNameErrors = [&]() -> void { + switch (mACNewNameError) { + case NameSelectionError::None: break; + case NameSelectionError::Duplicated: + ImGui::ErrorMessage("Duplicate template name"); + break; + case NameSelectionError::Empty: + ImGui::ErrorMessage("Template name cannot be empty"); + break; + } + }; + + auto ShowNewKindErrors = [&]() -> void { + if (mACNewKind == Template::InvalidKind) { + ImGui::ErrorMessage("Must select a valid template type"); + } + }; + + auto IsInputValid = [&]() -> bool { + return mACNewNameError == NameSelectionError::None && + mACNewKind != Template::InvalidKind; + }; + + auto ResetState = [&]() -> void { + mACNewName.clear(); + mACNewKind = Template::InvalidKind; + ValidateNewName(); + }; + + if (ImGui::InputText("Name", &mACNewName)) { + ValidateNewName(); + } + + if (ImGui::BeginCombo("Type", Template::FormatKind(mACNewKind))) { + for (int i = 0; i < Template::KindCount; ++i) { + auto kind = static_cast<Template::Kind>(i); + if (ImGui::Selectable(Template::FormatKind(kind), mACNewKind == kind)) { + mACNewKind = kind; + } + } + ImGui::EndCombo(); + } + + ShowNewNameErrors(); + ShowNewKindErrors(); + + if (ImGui::Button(ls->DialogConfirm.Get(), !IsInputValid())) { + ImGui::CloseCurrentPopup(); + + Create(SavedAsset{ + .Name = mACNewName, + .Payload = static_cast<uint64_t>(mACNewKind), + }); + ResetState(); + } + ImGui::SameLine(); + if (ImGui::Button(ls->DialogCancel.Get())) { + ImGui::CloseCurrentPopup(); + } +} + void TemplateAssetList::SetupDetailsTable(const char* tableId) const { - ImGui::BeginTable(tableId, 3); + ImGui::BeginTable(tableId, 2, ImGuiTableFlags_Borders); ImGui::TableSetupColumn("Name"); ImGui::TableSetupColumn("Type"); - ImGui::TableSetupColumn("Modified time"); ImGui::TableHeadersRow(); } -void TemplateAssetList::DrawBigIcon(const SavedAsset& asset) const +void TemplateAssetList::DrawBigIcon(ListState& state, const SavedAsset& asset) const { // TODO } -void TemplateAssetList::DrawDetailsTableRow(const SavedAsset& asset) const +void TemplateAssetList::DrawDetailsTableRow(ListState& state, const SavedAsset& asset) const { ImGui::TableNextColumn(); - ImGui::TextUnformatted(asset.Name.c_str()); + if (ImGui::Selectable(asset.Name.c_str(), state.SelectedAsset == &asset, ImGuiSelectableFlags_SpanAllColumns)) { + state.SelectedAsset = &asset; + } ImGui::TableNextColumn(); auto kind = static_cast<Template::Kind>(asset.Payload); ImGui::TextUnformatted(Template::FormatKind(kind)); - - ImGui::TableNextColumn(); - // TODO } diff --git a/core/src/Model/Workflow/Workflow.hpp b/core/src/Model/Workflow/Workflow.hpp index e5e434a..95dcafe 100644 --- a/core/src/Model/Workflow/Workflow.hpp +++ b/core/src/Model/Workflow/Workflow.hpp @@ -277,6 +277,11 @@ private: class WorkflowAssetList final : public AssetListTyped<Workflow> { +private: + // AC = Asset Creator + std::string mACNewName; + NameSelectionError mACNewNameError = NameSelectionError::Empty; + public: virtual void DiscoverFiles(const std::function<void(SavedAsset)>& callback) const override; @@ -289,7 +294,9 @@ public: virtual Workflow* LoadImpl(const SavedAsset& diskForm) const override; + virtual void DisplayAssetCreator(PopupState& state) override; + virtual void SetupDetailsTable(const char* tableId) const override; - virtual void DrawBigIcon(const SavedAsset& asset) const override; - virtual void DrawDetailsTableRow(const SavedAsset& asset) const override; + virtual void DrawBigIcon(ListState& state, const SavedAsset& asset) const override; + virtual void DrawDetailsTableRow(ListState& state, const SavedAsset& asset) const override; }; diff --git a/core/src/Model/Workflow/Workflow_Main.cpp b/core/src/Model/Workflow/Workflow_Main.cpp index 131e0ae..99c70a7 100644 --- a/core/src/Model/Workflow/Workflow_Main.cpp +++ b/core/src/Model/Workflow/Workflow_Main.cpp @@ -2,10 +2,13 @@ #include "Model/GlobalStates.hpp" #include "Model/Project.hpp" +#include "UI/Localization.hpp" +#include "UI/UI.hpp" #include "Utils/UUID.hpp" #include <imgui.h> #include <imgui_node_editor.h> +#include <imgui_stdlib.h> #include <tsl/robin_set.h> #include <cassert> #include <fstream> @@ -799,21 +802,82 @@ Workflow* WorkflowAssetList::LoadImpl(const SavedAsset& asset) const return workflow.release(); } +void WorkflowAssetList::DisplayAssetCreator(PopupState& state) +{ + auto ls = LocaleStrings::Instance.get(); + + auto ValidateNewName = [&]() -> void { + if (mACNewName.empty()) { + mACNewNameError = NameSelectionError::Empty; + return; + } + + if (FindByName(mACNewName)) { + mACNewNameError = NameSelectionError::Duplicated; + return; + } + + mACNewNameError = NameSelectionError::None; + }; + + auto ShowNewNameErrors = [&]() -> void { + switch (mACNewNameError) { + case NameSelectionError::None: break; + case NameSelectionError::Duplicated: + ImGui::ErrorMessage("Duplicate template name"); + break; + case NameSelectionError::Empty: + ImGui::ErrorMessage("Template name cannot be empty"); + break; + } + }; + + auto IsInputValid = [&]() -> bool { + return mACNewNameError == NameSelectionError::None; + }; + + auto ResetState = [&]() -> void { + mACNewName.clear(); + ValidateNewName(); + }; + + if (ImGui::InputText("Name", &mACNewName)) { + ValidateNewName(); + } + + ShowNewNameErrors(); + + if (ImGui::Button(ls->DialogConfirm.Get(), !IsInputValid())) { + ImGui::CloseCurrentPopup(); + + Create(SavedAsset{ + .Name = mACNewName, + }); + ResetState(); + } + ImGui::SameLine(); + if (ImGui::Button(ls->DialogCancel.Get())) { + ImGui::CloseCurrentPopup(); + } +} + void WorkflowAssetList::SetupDetailsTable(const char* tableId) const { - ImGui::BeginTable(tableId, 2); + ImGui::BeginTable(tableId, 1, ImGuiTableFlags_Borders); ImGui::TableSetupColumn("Name"); - ImGui::TableSetupColumn("Modified time"); ImGui::TableHeadersRow(); } -void WorkflowAssetList::DrawBigIcon(const SavedAsset& asset) const +void WorkflowAssetList::DrawBigIcon(ListState& state, const SavedAsset& asset) const { // TODO } -void WorkflowAssetList::DrawDetailsTableRow(const SavedAsset& asset) const +void WorkflowAssetList::DrawDetailsTableRow(ListState& state, const SavedAsset& asset) const { - // TODO + ImGui::TableNextColumn(); + if (ImGui::Selectable(asset.Name.c_str(), state.SelectedAsset == &asset, ImGuiSelectableFlags_SpanAllColumns)) { + state.SelectedAsset = &asset; + } } diff --git a/core/src/Model/fwd.hpp b/core/src/Model/fwd.hpp index 2028cbb..358fc49 100644 --- a/core/src/Model/fwd.hpp +++ b/core/src/Model/fwd.hpp @@ -13,6 +13,7 @@ class MainDatabase; // Assets.hpp struct SavedAsset; class Asset; +enum class NameSelectionError; class AssetList; // Filter.hpp |