diff options
Diffstat (limited to 'core')
-rw-r--r-- | core/CMakeLists.txt | 5 | ||||
-rw-r--r-- | core/locale/zh_CN.json | 8 | ||||
-rw-r--r-- | core/src/Entrypoint/main.cpp | 6 | ||||
-rw-r--r-- | core/src/Model/Project.cpp | 134 | ||||
-rw-r--r-- | core/src/Model/Project.hpp | 22 | ||||
-rw-r--r-- | core/src/Model/Template/TableTemplate.cpp | 63 | ||||
-rw-r--r-- | core/src/Model/Template/TableTemplate.hpp | 12 | ||||
-rw-r--r-- | core/src/Model/Template/Template.hpp | 45 | ||||
-rw-r--r-- | core/src/Model/Template/Template_Main.cpp | 5 | ||||
-rw-r--r-- | core/src/Model/Template/Template_RTTI.cpp | 21 | ||||
-rw-r--r-- | core/src/Model/Template/fwd.hpp | 4 | ||||
-rw-r--r-- | core/src/Model/Workflow/Workflow.hpp | 2 | ||||
-rw-r--r-- | core/src/Model/fwd.hpp | 2 | ||||
-rw-r--r-- | core/src/UI/Localization.hpp | 2 | ||||
-rw-r--r-- | core/src/UI/UI.hpp | 1 | ||||
-rw-r--r-- | core/src/UI/UI_MainWindow.cpp | 13 | ||||
-rw-r--r-- | core/src/UI/UI_Templates.cpp | 289 | ||||
-rw-r--r-- | core/src/UI/UI_Workflows.cpp | 12 |
18 files changed, 568 insertions, 78 deletions
diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 84f331e..a8fa7af 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -41,6 +41,8 @@ add_source_group(MODEL_MODULE_SOURCES ) add_source_group(MODEL_TEMPLATE_SOURCES + src/Model/Template/Template_Main.cpp + src/Model/Template/Template_RTTI.cpp src/Model/Template/TableTemplate.cpp ) @@ -67,11 +69,12 @@ set(UI_MODULE_SOURCES src/UI/Localization.cpp src/UI/States.cpp src/UI/UI_DatabaseView.cpp - src/UI/UI_Workflows.cpp src/UI/UI_Items.cpp src/UI/UI_MainWindow.cpp src/UI/UI_Settings.cpp + src/UI/UI_Templates.cpp src/UI/UI_Utils.cpp + src/UI/UI_Workflows.cpp ) add_source_group(UTILS_MODULE_SOURCES diff --git a/core/locale/zh_CN.json b/core/locale/zh_CN.json index e5df7ee..8e495d7 100644 --- a/core/locale/zh_CN.json +++ b/core/locale/zh_CN.json @@ -13,7 +13,8 @@ "MainWindow.Tab.Project": "\uf15b 项目", "MainWindow.Tab.DatabaseView": "\uf1c0 数据", "MainWindow.Tab.Items": "\uf466 物品", - "MainWindow.Tab.Workflows": "\uf5cb 工作流", + "MainWindow.Tab.Workflows": "\uf70e 工作流", + "MainWindow.Tab.Templates": "\uf0ce 模板", "Project.New": "新建项目...", "Project.New.DialogTitle": "新建项目向导", "Project.New.Name": "项目名称", @@ -28,8 +29,7 @@ "Project.Recents.NonePresent": "(暂无最近使用的项目)", "Project.Recents.Open.Tooltip": "打开该项目", "Project.Recents.Delete.Tooltip": "将该项目从最近使用列表中删除,项目本身将不受影响。", - "Project.InvalidProjectFormat": "无效的项目文件", - "ActiveProject.Close": "\uf00d 关闭项目", + "Project.InvalidProjectFormat": "无效的项目文件", "ActiveProject.OpenInFilesystem": "\uf07b 在文件系统中打开", "ActiveProject.Info.Name": "项目名称:", "ActiveProject.Info.Path": "项目路径:", @@ -65,4 +65,4 @@ "Workflow.Open.DialogTitle": "打开工作流", "Workflow.Manage": "管理工作流...", "Workflow.Manage.DialogTitle": "管理工作流", -}
\ No newline at end of file +} diff --git a/core/src/Entrypoint/main.cpp b/core/src/Entrypoint/main.cpp index 2d0238e..152a1c8 100644 --- a/core/src/Entrypoint/main.cpp +++ b/core/src/Entrypoint/main.cpp @@ -156,11 +156,7 @@ int main(int argc, char* argv[]) GlobalStates::Init(); } else { fs::path path(dataDirOption); - if (fs::exists(path)) { - GlobalStates::Init(std::move(path)); - } else { - GlobalStates::Init(); - } + GlobalStates::Init(std::move(path)); } DEFER { diff --git a/core/src/Model/Project.cpp b/core/src/Model/Project.cpp index 2d7c82a..3cb3cb4 100644 --- a/core/src/Model/Project.cpp +++ b/core/src/Model/Project.cpp @@ -25,19 +25,19 @@ void ReadItemList(ItemList<T>& list, const fs::path& filePath) } } -Project::Project(const fs::path& rootPath) - : mRootPath{ rootPath } +Project::Project(fs::path rootPath) + : mRootPath{ std::move(rootPath) } , mRootPathString{ mRootPath.string() } , Database(*this) { // TODO better diagnostic const char* kInvalidFormatErr = "Failed to load project: invalid format."; - std::ifstream ifs(rootPath / "cplt_project.json"); + std::ifstream ifs(mRootPath / "cplt_project.json"); if (!ifs) { std::string message; message += "Failed to load project file at '"; - message += rootPath.string(); + message += mRootPath.string(); message += "'."; throw std::runtime_error(message); } @@ -81,7 +81,7 @@ Project::Project(const fs::path& rootPath) } } -Project::Project(std::filesystem::path rootPath, std::string name) +Project::Project(fs::path rootPath, std::string name) : mRootPath{ std::move(rootPath) } , mRootPathString{ mRootPath.string() } , mName{ std::move(name) } @@ -99,6 +99,36 @@ const std::string& Project::GetPathString() const return mRootPathString; } +fs::path Project::GetDatabasesDirectory() const +{ + return mRootPath / "databases"; +} + +fs::path Project::GetItemsDirectory() const +{ + return mRootPath / "items"; +} + +fs::path Project::GetWorkflowsDirectory() const +{ + return mRootPath / "workflows"; +} + +fs::path Project::GetWorkflowPath(std::string_view name) const +{ + return (mRootPath / "workflows" / name).concat(".cplt-workflow"); +} + +fs::path Project::GetTemplatesDirectory() const +{ + return mRootPath / "templates"; +} + +fs::path Project::GetTemplatePath(std::string_view name) const +{ + return (mRootPath / "templates" / name).concat(".cplt-template"); +} + const std::string& Project::GetName() const { return mName; @@ -109,7 +139,7 @@ void Project::SetName(std::string name) mName = std::move(name); } -const decltype(Project::mWorkflows)& Project::GetWorkflows() const +const tsl::array_map<char, WorkflowInfo>& Project::GetWorkflows() const { return mWorkflows; } @@ -132,11 +162,12 @@ std::unique_ptr<Workflow> Project::CreateWorkflow(std::string_view name) } auto workflow = std::make_unique<Workflow>(); - auto [it, DISCARD] = mWorkflows.insert(name, WorkflowInfo{}); - auto& info = it.value(); - - info.Name = name; - info.Path = GetWorkflowPath(name); + auto [it, DISCARD] = mWorkflows.insert( + name, + WorkflowInfo{ + .Path = GetWorkflowPath(name), + .Name = std::string(name), + }); return workflow; } @@ -155,9 +186,9 @@ bool Project::RemoveWorkflow(std::string_view name) return true; } -bool Project::RenameWorkflow(std::string_view name, std::string_view newName) +bool Project::RenameWorkflow(std::string_view oldName, std::string_view newName) { - auto iter = mWorkflows.find(name); + auto iter = mWorkflows.find(oldName); if (iter == mWorkflows.end()) return false; auto info = std::move(iter.value()); @@ -173,6 +204,63 @@ bool Project::RenameWorkflow(std::string_view name, std::string_view newName) return true; } +const tsl::array_map<char, TemplateInfo>& Project::GetTemplates() const +{ + return mTemplates; +} + +std::unique_ptr<Template> Project::LoadTemplate(std::string_view name) +{ + auto iter = mTemplates.find(name); + if (iter == mTemplates.end()) { + return iter.value().LoadFromDisk(); + } else { + return nullptr; + } +} + +TemplateInfo* Project::InsertTemplate(std::string_view name, TemplateInfo info) +{ + auto [it, inserted] = mTemplates.insert(name, info); + if (inserted) { + return &it.value(); + } else { + return nullptr; + } +} + +bool Project::RemoveTemplate(std::string_view name) +{ + auto iter = mTemplates.find(name); + if (iter == mTemplates.end()) { + return false; + } + auto& info = iter.value(); + + fs::remove(info.Path); + mTemplates.erase(iter); + + return true; +} + +bool Project::RenameTemplate(std::string_view oldName, std::string_view newName) +{ + auto iter = mTemplates.find(oldName); + if (iter == mTemplates.end()) return false; + + auto info = std::move(iter.value()); + + auto& oldPath = info.Path; + auto newPath = GetTemplatePath(newName); + fs::rename(oldPath, newPath); + info.Path = std::move(newPath); + + mTemplates.insert(newName, std::move(info)); + mTemplates.erase(iter); + + return true; +} + Json::Value Project::Serialize() { Json::Value root(Json::objectValue); @@ -201,23 +289,3 @@ void Project::WriteToDisk() WriteItemList(Factories, itemsDir / "factories.json"); WriteItemList(Customers, itemsDir / "customers.json"); } - -std::filesystem::path Project::GetDatabasesDirectory() const -{ - return mRootPath / "databases"; -} - -std::filesystem::path Project::GetItemsDirectory() const -{ - return mRootPath / "items"; -} - -std::filesystem::path Project::GetWorkflowsDirectory() const -{ - return mRootPath / "workflows"; -} - -std::filesystem::path Project::GetWorkflowPath(std::string_view name) const -{ - return (mRootPath / "workflows" / name).concat(".cplt-workflow"); -} diff --git a/core/src/Model/Project.hpp b/core/src/Model/Project.hpp index 5f26532..bce58c2 100644 --- a/core/src/Model/Project.hpp +++ b/core/src/Model/Project.hpp @@ -1,7 +1,9 @@ #pragma once #include "Model/Items.hpp" +#include "Model/Template/Template.hpp" #include "Model/TransactionsModel.hpp" +#include "Model/Workflow/Workflow.hpp" #include <json/forwards.h> #include <tsl/array_map.h> @@ -15,17 +17,21 @@ public: ItemList<ProductItem> Products; ItemList<FactoryItem> Factories; ItemList<CustomerItem> Customers; - TransactionModel Database; private: tsl::array_map<char, WorkflowInfo> mWorkflows; + tsl::array_map<char, TemplateInfo> mTemplates; std::filesystem::path mRootPath; std::string mRootPathString; std::string mName; + // This is put after the private fields, so that when TransactionModel's constructor runs, all of them will be initialized +public: + TransactionModel Database; + public: /// Load the project from a directory containing the cplt_project.json file. - Project(const std::filesystem::path& rootPath); + Project(std::filesystem::path rootPath); /// Create a project with the given name in the given path. Note that the path should be a directory that will contain the project files once created. /// This function assumes the given directory will exist and is empty. @@ -39,15 +45,23 @@ public: std::filesystem::path GetItemsDirectory() const; std::filesystem::path GetWorkflowsDirectory() const; std::filesystem::path GetWorkflowPath(std::string_view name) const; + std::filesystem::path GetTemplatesDirectory() const; + std::filesystem::path GetTemplatePath(std::string_view name) const; const std::string& GetName() const; void SetName(std::string name); - const decltype(mWorkflows)& GetWorkflows() const; + const tsl::array_map<char, WorkflowInfo>& GetWorkflows() const; std::unique_ptr<Workflow> LoadWorkflow(std::string_view name); std::unique_ptr<Workflow> CreateWorkflow(std::string_view name); bool RemoveWorkflow(std::string_view name); - bool RenameWorkflow(std::string_view name, std::string_view newName); + bool RenameWorkflow(std::string_view oldName, std::string_view newName); + + const tsl::array_map<char, TemplateInfo>& GetTemplates() const; + std::unique_ptr<Template> LoadTemplate(std::string_view name); + bool InsertTemplate(std::string_view name, TemplateInfo info); + bool RemoveTemplate(std::string_view name); + bool RenameTemplate(std::string_view oldName, std::string_view newName); Json::Value Serialize(); void WriteToDisk(); diff --git a/core/src/Model/Template/TableTemplate.cpp b/core/src/Model/Template/TableTemplate.cpp index 258bb37..0ad1cca 100644 --- a/core/src/Model/Template/TableTemplate.cpp +++ b/core/src/Model/Template/TableTemplate.cpp @@ -5,6 +5,7 @@ bool TableCell::IsDataHoldingCell() const { + return IsPrimaryCell() || !IsMergedCell(); } bool TableCell::IsPrimaryCell() const @@ -58,12 +59,12 @@ const TableTemplate& TableInstanciationParameters::GetTable() const int TableTemplate::GetTableWidth() const { - return mTableWidth; + return mColumnWidths.size(); } int TableTemplate::GetTableHeight() const { - return mTableHeight; + return mRowHeights.size(); } void TableTemplate::Resize(int newWidth, int newHeight) @@ -73,10 +74,13 @@ void TableTemplate::Resize(int newWidth, int newHeight) std::vector<TableCell> cells; cells.reserve(newWidth * newHeight); - int yEnd = std::min(mTableHeight, newHeight); - int xEnd = std::min(mTableWidth, newWidth); + int tableWidth = GetTableWidth(); + int tableHeight = GetTableHeight(); + + int yEnd = std::min(tableHeight, newHeight); + int xEnd = std::min(tableWidth, newWidth); for (int y = 0; y < yEnd; ++y) { - if (y >= mTableHeight) { + if (y >= tableHeight) { for (int x = 0; x < xEnd; ++x) { cells.push_back(TableCell{}); } @@ -84,7 +88,7 @@ void TableTemplate::Resize(int newWidth, int newHeight) } for (int x = 0; x < xEnd; ++x) { - if (x >= mTableWidth) { + if (x >= tableWidth) { cells.push_back(TableCell{}); } else { auto& cell = GetCell({ x, y }); @@ -96,9 +100,30 @@ void TableTemplate::Resize(int newWidth, int newHeight) mCells = std::move(cells); } +int TableTemplate::GetRowHeight(int row) const +{ + return mRowHeights[row]; +} + +void TableTemplate::SetRowHeight(int row, int height) +{ + mRowHeights[row] = height; +} + +int TableTemplate::GetColumnWidth(int column) const +{ + return mColumnWidths[column]; +} + +void TableTemplate::SetColumnWidth(int column, int width) +{ + mColumnWidths[column] = width; +} + const TableCell& TableTemplate::GetCell(Vec2i pos) const { - return mCells[pos.y * mTableWidth + pos.x]; + int tableWidth = GetTableWidth(); + return mCells[pos.y * tableWidth + pos.x]; } TableCell& TableTemplate::GetCell(Vec2i pos) @@ -194,7 +219,7 @@ lxw_worksheet* TableTemplate::InstanciateToExcelWorksheet(lxw_workbook* workbook // Not enough space to fit in this array group, update (or insert) the appropriate amount of generated rows int row = i; int count = param.size(); - generatedRanges.insert(row, count); + generatedRanges.try_emplace(row, count); } auto GetOffset = [&](int y) -> int { @@ -217,27 +242,33 @@ lxw_worksheet* TableTemplate::InstanciateToExcelWorksheet(lxw_workbook* workbook } }; - // Write/instanciate all array groups + // Write/instantiate all array groups for (size_t i = 0; i < mArrayGroups.size(); ++i) { auto& groupInfo = mArrayGroups[i]; auto& groupParams = params.ArrayGroups[i]; int rowCellCount = groupInfo.GetCount(); - int row = groupInfo.Row + GetOffset(groupInfo.Row); + int rowCount = groupParams.size(); + int baseRowIdx = groupInfo.Row + GetOffset(groupInfo.Row); // For each row that would be generated - for (auto& rowCells : groupParams) { + for (int rowIdx = 0; rowIdx < rowCount; ++rowIdx) { + auto& row = groupParams[rowIdx]; + // For each cell in the row - for (int i = 0; i < rowCellCount; ++i) { - // TODO spport merged cells in array groups - worksheet_write_string(worksheet, row + i, col, rowCells[i].c_str(), nullptr); + for (int rowCellIdx = 0; rowCellIdx < rowCellCount; ++rowCellIdx) { + // TODO support merged cells in array groups + worksheet_write_string(worksheet, baseRowIdx + rowIdx, rowCellIdx, row[rowCellIdx].c_str(), nullptr); } } } + int tableWidth = GetTableWidth(); + int tableHeight = GetTableHeight(); + // Write all regular and singular parameter cells - for (int y = 0; y < mTableHeight; ++y) { - for (int x = 0; x < mTableWidth; ++x) { + for (int y = 0; y < tableHeight; ++y) { + for (int x = 0; x < tableWidth; ++x) { auto& cell = GetCell({ x, y }); if (!cell.IsDataHoldingCell()) { diff --git a/core/src/Model/Template/TableTemplate.hpp b/core/src/Model/Template/TableTemplate.hpp index be043a9..688192a 100644 --- a/core/src/Model/Template/TableTemplate.hpp +++ b/core/src/Model/Template/TableTemplate.hpp @@ -1,5 +1,6 @@ #pragma once +#include "Model/Template/Template.hpp" #include "Utils/Vector.hpp" #include "Utils/VectorHash.hpp" #include "cplt_fwd.hpp" @@ -127,19 +128,24 @@ public: /// parametric rows/columns, and grids are also supported. /// /// This current supports exporting to xlsx files. -class TableTemplate +class TableTemplate : public Template { private: std::vector<TableCell> mCells; std::vector<TableCellArrayGroup> mArrayGroups; - int mTableWidth; - int mTableHeight; + std::vector<int> mRowHeights; + std::vector<int> mColumnWidths; public: int GetTableWidth() const; int GetTableHeight() const; void Resize(int newWidth, int newHeight); + int GetRowHeight(int row) const; + void SetRowHeight(int row, int height); + int GetColumnWidth(int column) const; + void SetColumnWidth(int column, int width); + const TableCell& GetCell(Vec2i pos) const; TableCell& GetCell(Vec2i pos); diff --git a/core/src/Model/Template/Template.hpp b/core/src/Model/Template/Template.hpp new file mode 100644 index 0000000..0901a1b --- /dev/null +++ b/core/src/Model/Template/Template.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include "cplt_fwd.hpp" + +#include <filesystem> +#include <iosfwd> +#include <memory> +#include <string> + +class Template +{ +public: + enum Kind + { + KD_Table, + + InvalidKind, + KindCount = InvalidKind, + }; + +public: + static const char* FormatKind(Kind kind); + static std::unique_ptr<Template> CreateByKind(Kind kind); + + virtual ~Template() = default; + + enum class ReadResult + { + RR_Success, + RR_InvalidFormat, + }; + ReadResult ReadFrom(std::istream& stream) = 0; + void WriteTo(std::ostream& stream) const = 0; +}; + +class TemplateInfo +{ +public: + std::filesystem::path Path; + std::string Name; + std::string PathStringCache = Path.string(); + Template::Kind Kind; + + std::unique_ptr<Template> LoadFromDisk() const; +}; diff --git a/core/src/Model/Template/Template_Main.cpp b/core/src/Model/Template/Template_Main.cpp new file mode 100644 index 0000000..eeb6871 --- /dev/null +++ b/core/src/Model/Template/Template_Main.cpp @@ -0,0 +1,5 @@ +#include "Template.hpp" + +std::unique_ptr<Template> TemplateInfo::LoadFromDisk() const +{ +} diff --git a/core/src/Model/Template/Template_RTTI.cpp b/core/src/Model/Template/Template_RTTI.cpp new file mode 100644 index 0000000..042aaec --- /dev/null +++ b/core/src/Model/Template/Template_RTTI.cpp @@ -0,0 +1,21 @@ +#include "Template.hpp" + +#include "Model/Template/TableTemplate.hpp" + +inline const char* Template::FormatKind(Kind kind) +{ + switch (kind) { + case KD_Table: return "Table template"; + + case InvalidKind: return "<invalid kind>"; + } +} + +inline std::unique_ptr<Template> Template::CreateByKind(Kind kind) +{ + switch (kind) { + case KD_Table: return std::make_unique<TableTemplate>(); + + case InvalidKind: return nullptr; + } +} diff --git a/core/src/Model/Template/fwd.hpp b/core/src/Model/Template/fwd.hpp index 2f41f22..6bc7349 100644 --- a/core/src/Model/Template/fwd.hpp +++ b/core/src/Model/Template/fwd.hpp @@ -5,3 +5,7 @@ class TableCell; class TableCellArrayGroup; class TableInstanciationParameters; class TableTemplate; + +// Template.hpp +class Template; +class TemplateInfo; diff --git a/core/src/Model/Workflow/Workflow.hpp b/core/src/Model/Workflow/Workflow.hpp index 99e4e90..ded9bfb 100644 --- a/core/src/Model/Workflow/Workflow.hpp +++ b/core/src/Model/Workflow/Workflow.hpp @@ -173,9 +173,9 @@ protected: struct WorkflowInfo { + std::filesystem::path Path; std::string Name; std::string PathStringCache = Path.string(); - std::filesystem::path Path; std::unique_ptr<Workflow> LoadFromDisk() const; }; diff --git a/core/src/Model/fwd.hpp b/core/src/Model/fwd.hpp index 6fa29c5..09b0c75 100644 --- a/core/src/Model/fwd.hpp +++ b/core/src/Model/fwd.hpp @@ -3,6 +3,8 @@ #include "Model/Template/fwd.hpp" #include "Model/Workflow/fwd.hpp" +// Assets.hpp + // Filter.hpp class TableRowsFilter; diff --git a/core/src/UI/Localization.hpp b/core/src/UI/Localization.hpp index c470fa7..43ee23b 100644 --- a/core/src/UI/Localization.hpp +++ b/core/src/UI/Localization.hpp @@ -32,6 +32,7 @@ public: BasicTranslation DatabaseViewTab{ "MainWindow.Tab.DatabaseView"sv }; BasicTranslation ItemsTab{ "MainWindow.Tab.Items"sv }; BasicTranslation WorkflowsTab{ "MainWindow.Tab.Workflows"sv }; + BasicTranslation TemplatesTab{ "MainWindow.Tab.Templates"sv }; /* Project tab */ @@ -54,7 +55,6 @@ public: BasicTranslation InvalidProjectFormat{ "Project.InvalidProjectFormat"sv }; - BasicTranslation CloseActiveProject{ "ActiveProject.Close"sv }; BasicTranslation OpenActiveProjectInFileSystem{ "ActiveProject.OpenInFilesystem"sv }; BasicTranslation ActiveProjectName{ "ActiveProject.Info.Name"sv }; BasicTranslation ActiveProjectPath{ "ActiveProject.Info.Path"sv }; diff --git a/core/src/UI/UI.hpp b/core/src/UI/UI.hpp index cce0e00..dfce713 100644 --- a/core/src/UI/UI.hpp +++ b/core/src/UI/UI.hpp @@ -41,5 +41,6 @@ void SettingsTab(); void DatabaseViewTab(); void ItemsTab(); void WorkflowsTab(); +void TemplatesTab(); } // namespace UI diff --git a/core/src/UI/UI_MainWindow.cpp b/core/src/UI/UI_MainWindow.cpp index b1bee7d..1838aec 100644 --- a/core/src/UI/UI_MainWindow.cpp +++ b/core/src/UI/UI_MainWindow.cpp @@ -21,7 +21,7 @@ void ProjectTab_Normal() auto& gs = GlobalStates::GetInstance(); auto& uis = UIState::GetInstance(); - if (ImGui::Button(ls->CloseActiveProject.Get())) { + if (ImGui::Button(ls->Close.Get())) { uis.CloseCurrentProject(); return; } @@ -93,9 +93,9 @@ void ProjectTab_NoProject() uis.SetCurrentProject(std::make_unique<Project>(std::move(dirPath), std::move(projectName))); // Dialog just got closed, reset states - projectName = ""; - dirName = ""; - dirPath = fs::path{}; + projectName.clear(); + dirName.clear(); + dirPath.clear(); dirNameIsValid = false; } @@ -231,6 +231,11 @@ void UI::MainWindow() ImGui::EndTabItem(); } + if (ImGui::BeginTabItem(ls->TemplatesTab.Get())) { + UI::TemplatesTab(); + ImGui::EndTabItem(); + } + endTab: ImGui::EndTabBar(); } diff --git a/core/src/UI/UI_Templates.cpp b/core/src/UI/UI_Templates.cpp new file mode 100644 index 0000000..628f3f7 --- /dev/null +++ b/core/src/UI/UI_Templates.cpp @@ -0,0 +1,289 @@ +#include "UI.hpp" + +#include "Model/Project.hpp" +#include "Model/Template/TableTemplate.hpp" +#include "Model/Template/Template.hpp" +#include "UI/Localization.hpp" +#include "UI/States.hpp" + +#include <imgui.h> +#include <imgui_extra_math.h> +#include <imgui_stdlib.h> +#include <fstream> +#include <iostream> +#include <utility> + +namespace { +class TemplateUI +{ +public: + virtual ~TemplateUI() = default; + virtual void Draw() = 0; +}; + +class TableTemplateUI : public TemplateUI +{ +private: + std::unique_ptr<TableTemplate> mTable; + +public: + TableTemplateUI(std::unique_ptr<TableTemplate> table) + : mTable{ std::move(table) } + { + } + + virtual void Draw() override + { + ImGui::Columns(2); + + DrawInspector(); + ImGui::NextColumn(); + + DrawTable(); + ImGui::NextColumn(); + + ImGui::Columns(1); + } + +private: + void DrawInspector() + { + } + + void DrawTable() + { + constexpr int kCellSpacing = 20; + + int colCount = mTable->GetTableWidth(); + int rowCount = mTable->GetTableHeight(); + float x = 0.0f; + float y = 0.0f; + for (int rowIdx = 0; rowIdx < rowCount; ++rowIdx) { + int rowHeight = mTable->GetRowHeight(rowIdx); + + for (int colIdx = 0; colIdx < colCount; ++colIdx) { + int colWidth = mTable->GetColumnWidth(colIdx); + + ImRect rect{ + ImVec2(x, y), + ImVec2(colWidth, rowHeight), + }; + + // TODO + + x += colWidth; + x += kCellSpacing; + } + + y += rowHeight; + y += kCellSpacing; + } + } +}; + +struct DrawTemplateList_State +{ + // Internal data + + // Items that are intended for user usage + const TemplateInfo* SelectedTemplate = nullptr; +}; + +void DrawTemplateList(DrawTemplateList_State& state) +{ + auto& uis = UIState::GetInstance(); + auto& templates = uis.CurrentProject->GetTemplates(); + + for (auto& info : templates) { + if (ImGui::Selectable(info.Name.c_str(), state.SelectedTemplate == &info)) { + state.SelectedTemplate = &info; + } + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::Text("Path: %s", info.PathStringCache.c_str()); + ImGui::EndTooltip(); + } + } +} +} // namespace + +void UI::TemplatesTab() +{ + auto ls = LocaleStrings::Instance.get(); + auto& uis = UIState::GetInstance(); + + bool openedDummy = true; + static std::unique_ptr<TemplateUI> openTemplate; + static DrawTemplateList_State state; + + // Toolbar item: close + if (ImGui::Button(ls->Close.Get(), openTemplate == nullptr)) { + openTemplate = nullptr; + } + + // Toolbar item: open... + ImGui::SameLine(); + if (ImGui::Button("Open...")) { + ImGui::OpenPopup("Open template"); + } + if (ImGui::BeginPopupModal("Open template", &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { + DrawTemplateList(state); + + if (state.SelectedTemplate) { + // TODO + } + + ImGui::EndPopup(); + } + + // Toolbar item: manage... + ImGui::SameLine(); + if (ImGui::Button("Manage...")) { + ImGui::OpenPopup("Manage templates"); + } + if (ImGui::BeginPopupModal("Manage templates", &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { + DrawTemplateList(state); + + enum class NameSelectionError + { + None, + Duplicated, + Empty, + }; + + static std::string newName; + static NameSelectionError newNameError; + auto ValidateNewName = [&]() -> void { + if (newName.empty()) { + newNameError = NameSelectionError::Empty; + } + + auto& templates = uis.CurrentProject->GetTemplates(); + if (templates.find(newName) != templates.end()) { + newNameError = NameSelectionError::Duplicated; + } + }; + auto ShowNewNameErrors = [&]() -> void { + 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; + } + }; + + static Template::Kind newKind = Template::InvalidKind; + auto ShowNewKindErrors = [&]() -> void { + if (newKind == Template::InvalidKind) { + ImGui::ErrorMessage("Must select a valid template type"); + } + }; + + auto IsInputValid = [&]() -> bool { + return newNameError == NameSelectionError::None && newKind != Template::InvalidKind; + }; + + if (ImGui::Button(ls->Add.Get())) { + ImGui::OpenPopup("Create template"); + } + if (ImGui::BeginPopupModal("Create template")) { + if (ImGui::InputText("Name", &newName)) { + ValidateNewName(); + } + + if (ImGui::BeginCombo("Type", Template::FormatKind(newKind))) { + for (int i = 0; i < Template::KindCount; ++i) { + auto kind = static_cast<Template::Kind>(i); + if (ImGui::Selectable(Template::FormatKind(kind), newKind == kind)) { + newKind = kind; + } + } + ImGui::EndCombo(); + } + + if (ImGui::Button(ls->DialogConfirm.Get(), IsInputValid())) { + auto& project = *uis.CurrentProject; + + auto& info = *project.InsertTemplate( + newName, + TemplateInfo{ + .Path = project.GetTemplatePath(newName), + .Name = newName, // Don't std::move here because evaluation order of `newName` (as parameter of InsertTemplate()) and this is unspecified + .Kind = newKind, + }); + + auto tmpl = Template::CreateByKind(newKind); + + std::ofstream ofs(info.Path); + if (ofs) { + tmpl->WriteTo(ofs); + } else { + // Writing to disk here isn't necessary for downstream operations to function successfully; + // if the user makes any changes and don't save, those changes will not persist anyways + // if the user doesn't make any changes, it doesn't matter that the empty template file isn't created yet + } + + openTemplate = std::make_unique<TableTemplateUI>(std::move(tmpl)); + } + ImGui::SameLine(); + if (ImGui::Button(ls->DialogCancel.Get())) { + ImGui::CloseCurrentPopup(); + } + + ShowNewNameErrors(); + ShowNewKindErrors(); + + ImGui::EndPopup(); + } + + if (ImGui::Button(ls->Rename.Get(), state.SelectedTemplate == nullptr)) { + ImGui::OpenPopup("Rename template"); + newName.clear(); + } + if (ImGui::BeginPopupModal("Rename template")) { + if (ImGui::InputText("New name", &newName)) { + ValidateNewName(); + } + + if (ImGui::Button(ls->DialogConfirm.Get(), IsInputValid())) { + auto& project = *uis.CurrentProject; + + project.RenameTemplate( + state.SelectedTemplate->Name, + newName); + + // We mutated the map, the pointer may be invalid now + state.SelectedTemplate = &project.GetTemplates().at(newName); + } + ImGui::SameLine(); + if (ImGui::Button(ls->DialogCancel.Get())) { + ImGui::CloseCurrentPopup(); + } + + ShowNewNameErrors(); + + ImGui::EndPopup(); + } + + if (ImGui::Button(ls->Delete.Get(), state.SelectedTemplate == nullptr)) { + ImGui::OpenPopup("Delete confirmation"); + } + if (ImGui::BeginPopupModal("Delete confirmation")) { + assert(state.SelectedTemplate != nullptr); + + if (ImGui::Button(ls->DialogConfirm.Get())) { + uis.CurrentProject->RemoveTemplate(state.SelectedTemplate->Name); + } + ImGui::SameLine(); + if (ImGui::Button(ls->DialogCancel.Get())) { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + + ImGui::EndPopup(); + } +} diff --git a/core/src/UI/UI_Workflows.cpp b/core/src/UI/UI_Workflows.cpp index feebe89..ac023c9 100644 --- a/core/src/UI/UI_Workflows.cpp +++ b/core/src/UI/UI_Workflows.cpp @@ -359,12 +359,12 @@ public: } }; -struct DrawWorkflowList_State +struct DrawTemplateList_State { const WorkflowInfo* SelectedWorkflow = nullptr; }; -void DrawWorkflowList(DrawWorkflowList_State& state) +void DrawTemplateList(DrawTemplateList_State& state) { auto& uis = UIState::GetInstance(); auto& workflows = uis.CurrentProject->GetWorkflows(); @@ -390,7 +390,7 @@ void UI::WorkflowsTab() bool openedDummy = true; static std::unique_ptr<WorkflowUI> openWorkflow; - static DrawWorkflowList_State state; + static DrawTemplateList_State state; // Toolbar item: close if (ImGui::Button(ls->Close.Get(), openWorkflow == nullptr)) { @@ -403,7 +403,7 @@ void UI::WorkflowsTab() ImGui::OpenPopup(ls->OpenWorkflowDialogTitle.Get()); } if (ImGui::BeginPopupModal(ls->OpenWorkflowDialogTitle.Get(), &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { - DrawWorkflowList(state); + DrawTemplateList(state); if (state.SelectedWorkflow) { auto workflow = state.SelectedWorkflow->LoadFromDisk(); @@ -419,7 +419,7 @@ void UI::WorkflowsTab() ImGui::OpenPopup(ls->ManageWorkflowsDialogTitle.Get()); } if (ImGui::BeginPopupModal(ls->ManageWorkflowsDialogTitle.Get(), &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { - DrawWorkflowList(state); + DrawTemplateList(state); enum class NameSelectionError { @@ -448,7 +448,7 @@ void UI::WorkflowsTab() if (ImGui::Button(ls->DialogConfirm.Get(), newName.empty())) { auto& project = uis.CurrentProject; project->RenameWorkflow(state.SelectedWorkflow->Name, newName); - state.SelectedWorkflow = &project->GetWorkflows()[newName]; + state.SelectedWorkflow = &project->GetWorkflows().at(newName); } ImGui::SameLine(); if (ImGui::Button(ls->DialogCancel.Get())) { |