diff options
author | rtk0c <[email protected]> | 2021-03-30 19:40:11 -0700 |
---|---|---|
committer | rtk0c <[email protected]> | 2021-03-30 19:40:11 -0700 |
commit | 31950890c939862f79c817053c106bf711c63a64 (patch) | |
tree | 4e02abf37d69ab7d4f988f143b340cfd3d93331c | |
parent | e75e26da92424528e190a2111acfcc49c657e894 (diff) |
Product items and misc stuff
-rw-r--r-- | core/CMakeLists.txt | 26 | ||||
-rw-r--r-- | core/locale/zh_CN.json | 16 | ||||
-rw-r--r-- | core/src/Entrypoint/main.cpp | 6 | ||||
-rw-r--r-- | core/src/Model/Items.cpp | 83 | ||||
-rw-r--r-- | core/src/Model/Items.hpp | 146 | ||||
-rw-r--r-- | core/src/Model/Project.cpp | 22 | ||||
-rw-r--r-- | core/src/Model/Project.hpp | 13 | ||||
-rw-r--r-- | core/src/Model/fwd.hpp | 8 | ||||
-rw-r--r-- | core/src/UI/Localization.hpp | 22 | ||||
-rw-r--r-- | core/src/UI/States.cpp | 9 | ||||
-rw-r--r-- | core/src/UI/States.hpp | 1 | ||||
-rw-r--r-- | core/src/UI/UI.hpp | 8 | ||||
-rw-r--r-- | core/src/UI/UI_Items.cpp | 117 | ||||
-rw-r--r-- | core/src/UI/UI_MainWindow.cpp | 67 | ||||
-rw-r--r-- | core/src/UI/UI_Utils.cpp | 21 |
15 files changed, 520 insertions, 45 deletions
diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index d8e9db1..f71cdd4 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -17,7 +17,7 @@ endif() function(add_source_group GROUP_NAME) set(${GROUP_NAME} ${ARGN} PARENT_SCOPE) set_source_files_properties(${ARGN} - PROPERTIES + PROPERTIES UNITY_GROUP "${GROUP_NAME}" ) endfunction() @@ -35,6 +35,7 @@ set(ENTRYPOINT_MODULE_SOURCES add_source_group(MODEL_MODULE_SOURCES src/Model/GlobalStates.cpp + src/Model/Items.cpp src/Model/Project.cpp src/Model/Stock.cpp ) @@ -136,15 +137,32 @@ function(add_executable_variant TARGET_NAME) endif() if(WIN32) - # No console window when targetting windows - if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + # No console window when targeting windows + function(handle_gnu_style_compiler) # Supposedly the flag -mwindows would automatically make the executable use GUI subsystem # But, when subsystem is set to GUI, linker will only search WinMain and wWinMain but not the standard main (it seems like) # so creating GUI executable fails and the linker silently reverts to the default, CUI subsystem target_link_options(${TARGET_NAME} PRIVATE -Wl,-subsystem:windows) target_link_options(${TARGET_NAME} PRIVATE -Wl,-entry:mainCRTStartup) - elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + endfunction() + + function(handle_msvc_style_compiler) target_link_options(${TARGET_NAME} PRIVATE /SUBSYSTEM:windows /ENTRY:mainCRTStartup) + endfunction() + + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + # GCC (MinGW) + handle_gnu_style_compiler() + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + if("x${CMAKE_CXX_SIMULATE_ID}" STREQUAL "xMSVC") + # MSVC-style argument clang (clang-cl.exe) + handle_msvc_style_compiler() + else() + # GNU-style argument clang (clang.exe and clang++.exe) + handle_gnu_style_compiler() + endif() + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + handle_msvc_style_compiler() endif() endif() diff --git a/core/locale/zh_CN.json b/core/locale/zh_CN.json index 3b38f40..ebd671e 100644 --- a/core/locale/zh_CN.json +++ b/core/locale/zh_CN.json @@ -1,5 +1,7 @@ { "$localized_name": "中文 - 中国", + "Generic.Dialog.Confirm": "确定", + "Generic.Dialog.Cancel": "取消", "MainWindow.Tab.Settings": "\uf013 设置", "MainWindow.Tab.Project": "\uf15b 项目", "MainWindow.Tab.DatabaseView": "\uf1c0 数据", @@ -7,8 +9,6 @@ "MainWindow.Tab.Exports": "\uf56e 导出", "Project.New": "新建项目...", "Project.New.DialogTitle": "新建项目向导", - "Project.New.Confirm": "确定", - "Project.New.Cancel": "取消", "Project.New.Name": "项目名称", "Project.New.Path": "项目路径", "Project.New.Path.DialogTitle": "项目路径", @@ -21,4 +21,16 @@ "Project.Recents.NonePresent": "(暂无最近使用的项目)", "Project.Recents.Open.Tooltip": "打开该项目", "Project.Recents.Delete.Tooltip": "将该项目从最近使用列表中删除,项目本身将不受影响。", + "ActiveProject.Close": "\uf410 关闭项目", + "ActiveProject.OpenInFilesystem": "\uf07b 在文件系统中打开", + "ActiveProject.Info.Name": "项目名称:", + "ActiveProject.Info.Path": "项目路径:", + "ItemEditor.Add": "\uf067 新建", + "ItemEditor.Add.DialogTitle": "新建物品项", + "ItemEditor.Delete": "\uf1f8 移除", + "Item.Product.CategoryName": "产品", + "Item.Product.Column.Name": "名称", + "Item.Product.Column.Description": "描述", + "Item.Factory.CategoryName": "工厂", + "Item.Customer.CategoryName": "客户", }
\ No newline at end of file diff --git a/core/src/Entrypoint/main.cpp b/core/src/Entrypoint/main.cpp index ce8aab7..5811547 100644 --- a/core/src/Entrypoint/main.cpp +++ b/core/src/Entrypoint/main.cpp @@ -129,6 +129,8 @@ int main(int argc, char* argv[]) { auto backendOption = parser.get<std::string>("--rendering-backend"); auto backend = CreateBackend(backendOption); + ImGui::GetIO().IniFilename = nullptr; + ImGui::GetIO().LogFilename = nullptr; ImGui::StyleColorsLight(); // Includes latin alphabet, although for some reason smaller than if rendered using 18 point NotoSans regular @@ -140,7 +142,7 @@ int main(int argc, char* argv[]) { ImGui::GetIO().Fonts->AddFontFromFileTTF("fonts/FontAwesome5-Solid.otf", 14, &config, iconRanges); I18n::OnReload.Connect([]() { LocaleStrings::Instance = std::make_unique<LocaleStrings>(); }); - // Do i18n intialization after linking reload signals, so that when SetLanguage() is called, the locale strings will be initialized (without us writing the code another time outside the slot) + // Do i18n initialization after linking reload signals, so that when SetLanguage() is called, the locale strings will be initialized (without us writing the code another time outside the slot) I18n::Init(); I18n::SetLanguage("zh_CN"); @@ -151,9 +153,11 @@ int main(int argc, char* argv[]) { while (!glfwWindowShouldClose(window)) { backend->BeginFrame(); UI::MainWindow(); + ImGui::ShowDemoWindow(); backend->EndFrame(); } + UIState::Shutdown(); GlobalStates::Shutdown(); return 0; diff --git a/core/src/Model/Items.cpp b/core/src/Model/Items.cpp new file mode 100644 index 0000000..db2d39f --- /dev/null +++ b/core/src/Model/Items.cpp @@ -0,0 +1,83 @@ +#include "Items.hpp" + +#include <limits> +#include <utility> + +ItemBase::ItemBase() + : mId{ std::numeric_limits<size_t>::max() } { +} + +ItemBase::ItemBase(size_t id) + : mId{ id } { +} + +bool ItemBase::IsInvalid() const { + return mId == std::numeric_limits<size_t>::max(); +} + +size_t ItemBase::GetId() const { + return mId; +} + +ProductItem::ProductItem(size_t id, std::string name) + : ItemBase(id) + , mName{ std::move(name) } { +} + +const std::string& ProductItem::GetName() const { + return mName; +} + +void ProductItem::SetName(std::string name) { + mName = std::move(name); +} + +const std::string& ProductItem::GetDescription() const { + return mDescription; +} + +void ProductItem::SetDescription(std::string description) { + mDescription = std::move(description); +} + +FactoryItem::FactoryItem(size_t id, std::string name) + : ItemBase(id) + , mName{ std::move(name) } { +} + +const std::string& FactoryItem::GetName() const { + return mName; +} + +void FactoryItem::SetName(std::string name) { + mName = std::move(name); +} + +const std::string& FactoryItem::GetDescription() const { + return mDescription; +} + +void FactoryItem::SetDescription(std::string description) { + mDescription = std::move(description); +} + +CustomerItem::CustomerItem(size_t id, std::string name) + : ItemBase(id) + , mName{ std::move(name) } { +} + +const std::string& CustomerItem::GetName() const { + return mName; +} + +void CustomerItem::SetName(std::string name) { + mName = std::move(name); +} + +const std::string& CustomerItem::GetDescription() const { + return mDescription; +} + +void CustomerItem::SetDescription(std::string description) { + mDescription = std::move(description); +} diff --git a/core/src/Model/Items.hpp b/core/src/Model/Items.hpp new file mode 100644 index 0000000..0c7be41 --- /dev/null +++ b/core/src/Model/Items.hpp @@ -0,0 +1,146 @@ +#pragma once + +#include <tsl/array_map.h> +#include <cstddef> +#include <stdexcept> +#include <string> +#include <string_view> +#include <vector> + +/// Pointers and references returned by accessors are valid as long as no non-const functions have been called. +template <class T> +class ItemList { +public: + class Iterator { + private: + typename std::vector<T>::const_iterator mBackingIter; + + public: + Iterator(typename std::vector<T>::const_iterator it) + : mBackingIter{ it } { + } + + Iterator& operator++() { + ++mBackingIter; + return *this; + } + + Iterator& operator++(int) { + auto tmp = *this; + ++mBackingIter; + return tmp; + } + + Iterator& operator--() { + --mBackingIter; + return *this; + } + + Iterator& operator--(int) { + auto tmp = *this; + --mBackingIter; + return tmp; + } + + const T& operator*() const { + return *mBackingIter; + } + + friend bool operator==(const Iterator&, const Iterator&) = default; + }; + +private: + std::vector<T> mStorage; + tsl::array_map<char, size_t> mNameLookup; + +public: + template <class... Args> + T& Insert(std::string name, Args... args) { + auto iter = mNameLookup.find(name); + if (iter != mNameLookup.end()) { + throw std::runtime_error("Duplicate key."); + } + + size_t id = mStorage.size(); + mNameLookup.insert(name, id); + return mStorage.emplace_back(id, std::move(name), std::forward<Args>(args)...); + } + + const T* Find(size_t id) const { + return &mStorage[id]; + } + + const T* Find(std::string_view name) const { + auto iter = mNameLookup.find(name); + if (iter != mNameLookup.end()) { + return &mStorage[iter.value()]; + } else { + return nullptr; + } + } + + Iterator begin() const { + return Iterator(mStorage.begin()); + } + + Iterator end() const { + return Iterator(mStorage.end()); + } +}; + +class ItemBase { +private: + size_t mId; + +public: + ItemBase(); + ItemBase(size_t id); + + bool IsInvalid() const; + size_t GetId() const; +}; + +class ProductItem : public ItemBase { +private: + std::string mName; + std::string mDescription; + +public: + ProductItem() {} + ProductItem(size_t id, std::string name); + + const std::string& GetName() const; + void SetName(std::string mName); + const std::string& GetDescription() const; + void SetDescription(std::string description); +}; + +class FactoryItem : public ItemBase { +private: + std::string mName; + std::string mDescription; + +public: + FactoryItem() {} + FactoryItem(size_t id, std::string name); + + const std::string& GetName() const; + void SetName(std::string name); + const std::string& GetDescription() const; + void SetDescription(std::string description); +}; + +class CustomerItem : public ItemBase { +private: + std::string mName; + std::string mDescription; + +public: + CustomerItem() {} + CustomerItem(size_t id, std::string name); + + const std::string& GetName() const; + void SetName(std::string name); + const std::string& GetDescription() const; + void SetDescription(std::string description); +}; diff --git a/core/src/Model/Project.cpp b/core/src/Model/Project.cpp index c54a02c..f070940 100644 --- a/core/src/Model/Project.cpp +++ b/core/src/Model/Project.cpp @@ -25,11 +25,12 @@ Project Project::Load(const fs::path& path) { Project proj; proj.mRootPath = path.parent_path(); + proj.mRootPathString = proj.mRootPath.string(); Json::Value root; ifs >> root; - const auto& croot = root; // Use const reference so that accessors default to returning a null if not found, instead of siliently creating new elements + const auto& croot = root; // Use const reference so that accessors default to returning a null if not found, instead of silently creating new elements if (!croot.isObject()) { throw std::runtime_error(kInvalidFormatErr); } @@ -46,6 +47,7 @@ Project Project::Load(const fs::path& path) { Project Project::Create(std::string name, const fs::path& path) { Project proj; proj.mRootPath = path; + proj.mRootPathString = path.string(); proj.mName = std::move(name); return proj; } @@ -54,6 +56,10 @@ const fs::path& Project::GetPath() const { return mRootPath; } +const std::string& Project::GetPathString() const { + return mRootPathString; +} + const std::string& Project::GetName() const { return mName; } @@ -61,3 +67,17 @@ const std::string& Project::GetName() const { void Project::SetName(std::string name) { mName = std::move(name); } + +Json::Value Project::Serialize() { + Json::Value root(Json::objectValue); + + root["Name"] = mName; + + return root; +} + +void Project::WriteToDisk() { + auto root = Serialize(); + std::ofstream ofs(mRootPath / "cplt_project.json"); + ofs << root; +} diff --git a/core/src/Model/Project.hpp b/core/src/Model/Project.hpp index 7b5c7e3..23eafc1 100644 --- a/core/src/Model/Project.hpp +++ b/core/src/Model/Project.hpp @@ -1,11 +1,20 @@ #pragma once +#include "Model/Items.hpp" + #include <filesystem> #include <string> +#include <json/forwards.h> class Project { public: + ItemList<ProductItem> Products; + ItemList<FactoryItem> Factories; + ItemList<CustomerItem> Customers; + +private: std::filesystem::path mRootPath; + std::string mRootPathString; std::string mName; public: @@ -17,10 +26,14 @@ public: // Path to a *directory* that contains the project file. const std::filesystem::path& GetPath() const; + const std::string& GetPathString() const; const std::string& GetName() const; void SetName(std::string name); + Json::Value Serialize(); + void WriteToDisk(); + private: Project() = default; }; diff --git a/core/src/Model/fwd.hpp b/core/src/Model/fwd.hpp index 6bbc0b7..bf9a8cf 100644 --- a/core/src/Model/fwd.hpp +++ b/core/src/Model/fwd.hpp @@ -3,5 +3,13 @@ // GlobalStates.hpp class GlobalStates; +// Items.hpp +template <class T> +class ItemList; +class ItemBase; +class ProductItem; +class FactoryItem; +class CustomerItem; + // Project.hpp class Project; diff --git a/core/src/UI/Localization.hpp b/core/src/UI/Localization.hpp index e604165..d5424ea 100644 --- a/core/src/UI/Localization.hpp +++ b/core/src/UI/Localization.hpp @@ -12,6 +12,9 @@ public: static std::unique_ptr<LocaleStrings> Instance; public: + BasicTranslation DialogConfirm{ "Generic.Dialog.Confirm"sv }; + BasicTranslation DialogCancel{ "Generic.Dialog.Cancel"sv }; + BasicTranslation TabSettings{ "MainWindow.Tab.Settings"sv }; BasicTranslation TabProject{ "MainWindow.Tab.Project"sv }; BasicTranslation TabDatabaseView{ "MainWindow.Tab.DatabaseView"sv }; @@ -19,9 +22,7 @@ public: BasicTranslation TabExport{ "MainWindow.Tab.Exports"sv }; BasicTranslation NewProject{ "Project.New"sv }; - BasicTranslation NewProjectTitle{ "Project.New.DialogTitle"sv }; - BasicTranslation ConfirmNewProject{ "Project.New.Confirm"sv }; - BasicTranslation CancelNewProject{ "Project.New.Cancel"sv }; + BasicTranslation NewProjectDialogTitle{ "Project.New.DialogTitle"sv }; BasicTranslation NewProjectNameHint{ "Project.New.Name"sv }; BasicTranslation NewProjectPathHint{ "Project.New.Path"sv }; BasicTranslation NewProjectPathDialogTitle{ "Project.New.Path.DialogTitle"sv }; @@ -36,4 +37,19 @@ public: BasicTranslation NoRecentProjectsMessage{ "Project.Recents.NonePresent"sv }; BasicTranslation OpenRecentProjectTooltip{ "Project.Recents.Open.Tooltip"sv }; BasicTranslation DeleteRecentProjectTooltip{ "Project.Recents.Delete.Tooltip"sv }; + + BasicTranslation CloseActiveProject{ "ActiveProject.Close"sv }; + BasicTranslation OpenActiveProjectInFileSystem{ "ActiveProject.OpenInFilesystem"sv }; + BasicTranslation ActiveProjectName{ "ActiveProject.Info.Name"sv }; + BasicTranslation ActiveProjectPath{ "ActiveProject.Info.Path"sv }; + + BasicTranslation AddItem{ "ItemEditor.Add"sv }; + BasicTranslation AddItemDialogTitle{ "ItemEditor.Add.DialogTitle"sv }; + BasicTranslation DeleteItem{ "ItemEditor.Delete"sv }; + + BasicTranslation ProductCategoryName{ "Item.Product.CategoryName"sv }; + BasicTranslation ProductNameColumn{ "Item.Product.Column.Name"sv }; + BasicTranslation ProductDescriptionColumn{ "Item.Product.Column.Description"sv }; + BasicTranslation FactoryCategoryName{ "Item.Factory.CategoryName"sv }; + BasicTranslation CustomerCategoryName{ "Item.Customer.CategoryName"sv }; }; diff --git a/core/src/UI/States.cpp b/core/src/UI/States.cpp index 07bbcf7..dc7c37a 100644 --- a/core/src/UI/States.cpp +++ b/core/src/UI/States.cpp @@ -11,6 +11,13 @@ void UIState::Init() { uiStateInstance = std::make_unique<UIState>(); } +void UIState::Shutdown() { + if (uiStateInstance) { + uiStateInstance->CloseCurrentProject(); + uiStateInstance = nullptr; + } +} + UIState& UIState::GetInstance() { return *uiStateInstance; } @@ -22,7 +29,7 @@ void UIState::SetCurrentProject(std::unique_ptr<Project> project) { void UIState::CloseCurrentProject() { if (CurrentProject) { - // TODO save stuff + CurrentProject->WriteToDisk(); CurrentProject = nullptr; } } diff --git a/core/src/UI/States.hpp b/core/src/UI/States.hpp index cbb556f..de0510c 100644 --- a/core/src/UI/States.hpp +++ b/core/src/UI/States.hpp @@ -9,6 +9,7 @@ class UIState { public: static void Init(); + static void Shutdown(); static UIState& GetInstance(); public: diff --git a/core/src/UI/UI.hpp b/core/src/UI/UI.hpp index b0c3aaa..52a2ca9 100644 --- a/core/src/UI/UI.hpp +++ b/core/src/UI/UI.hpp @@ -1,7 +1,15 @@ #pragma once +#include <imgui.h> + namespace ImGui { +void SetNextWindowSizeRelScreen(float xPercent, float yPercent, ImGuiCond_ cond = ImGuiCond_None); +void SetNextWindowCentered(ImGuiCond_ cond = ImGuiCond_None); + +void PushDisabled(); +void PopDisabled(); + void ErrorIcon(); void WarningIcon(); diff --git a/core/src/UI/UI_Items.cpp b/core/src/UI/UI_Items.cpp index 371e682..a990a96 100644 --- a/core/src/UI/UI_Items.cpp +++ b/core/src/UI/UI_Items.cpp @@ -1,9 +1,124 @@ #include "UI.hpp" +#include "Model/GlobalStates.hpp" +#include "Model/Project.hpp" #include "UI/Localization.hpp" +#include "UI/States.hpp" +#include <IconsFontAwesome.h> #include <imgui.h> +#include <imgui_stdlib.h> -void UI::ItemsTab() { +namespace { +/// Specialized for each item type. +template <class T> +void AddToItemListDialog(ItemList<T>& list); +/// Specialized for each item type. +template <class T> +void ItemListEntries(ItemList<T>& list); + +template <> +void AddToItemListDialog<ProductItem>(ItemList<ProductItem>& list) { + static std::string productName; + static std::string description; + + auto ls = LocaleStrings::Instance.get(); + auto& uis = UIState::GetInstance(); + ImGui::InputText(ls->ProductNameColumn.Get(), &productName); + ImGui::InputText(ls->ProductDescriptionColumn.Get(), &description); + + if (ImGui::Button(ls->DialogConfirm.Get())) { + auto& product = uis.CurrentProject->Products.Insert(std::move(productName)); + product.SetDescription(std::move(description)); + + productName.clear(); + description.clear(); + + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button(ls->DialogCancel.Get())) { + ImGui::CloseCurrentPopup(); + } +} + +template <> +void AddToItemListDialog<FactoryItem>(ItemList<FactoryItem>& list) { // TODO } + +template <> +void AddToItemListDialog<CustomerItem>(ItemList<CustomerItem>& list) { + // TODO +} + +template <> +void ItemListEntries<ProductItem>(ItemList<ProductItem>& list) { + auto ls = LocaleStrings::Instance.get(); + if (ImGui::BeginTable("ItemListEntries", 2)) { + + ImGui::TableSetupColumn(ls->ProductNameColumn.Get()); + ImGui::TableSetupColumn(ls->ProductDescriptionColumn.Get()); + ImGui::TableHeadersRow(); + + for (auto& entry : list) { + ImGui::TableNextRow(); + + ImGui::TableNextColumn(); + ImGui::Text("%s", entry.GetName().c_str()); + ImGui::TableNextColumn(); + ImGui::Text("%.8s", entry.GetDescription().c_str()); + if (ImGui::Button(ICON_FA_EDIT)) { + // TODO + } + } + ImGui::EndTable(); + } +} + +template <> +void ItemListEntries<FactoryItem>(ItemList<FactoryItem>& list) { + // TODO +} + +template <> +void ItemListEntries<CustomerItem>(ItemList<CustomerItem>& list) { + // TODO +} + +template <class T> +void ItemListEditor(ItemList<T>& list) { + auto ls = LocaleStrings::Instance.get(); + + if (ImGui::Button(ls->AddItem.Get())) { + ImGui::SetNextWindowCentered(); + ImGui::OpenPopup(ls->AddItemDialogTitle.Get()); + } + if (ImGui::BeginPopupModal(ls->AddItemDialogTitle.Get())) { + AddToItemListDialog<T>(list); + ImGui::EndPopup(); + } + + ImGui::SameLine(); + if (ImGui::Button(ls->DeleteItem.Get())) { + // TODO + } + + ItemListEntries<T>(list); +} +} // namespace + +void UI::ItemsTab() { + auto ls = LocaleStrings::Instance.get(); + auto& uis = UIState::GetInstance(); + + if (ImGui::CollapsingHeader(ls->ProductCategoryName.Get())) { + ItemListEditor(uis.CurrentProject->Products); + } + if (ImGui::CollapsingHeader(ls->FactoryCategoryName.Get())) { + ItemListEditor(uis.CurrentProject->Factories); + } + if (ImGui::CollapsingHeader(ls->CustomerCategoryName.Get())) { + ItemListEditor(uis.CurrentProject->Customers); + } +} diff --git a/core/src/UI/UI_MainWindow.cpp b/core/src/UI/UI_MainWindow.cpp index de300e2..15c28ff 100644 --- a/core/src/UI/UI_MainWindow.cpp +++ b/core/src/UI/UI_MainWindow.cpp @@ -5,28 +5,38 @@ #include "UI/Localization.hpp" #include "UI/States.hpp" -#include <portable-file-dialogs.h> - #include <IconsFontAwesome.h> #include <imgui.h> -#include <imgui_internal.h> #include <imgui_stdlib.h> +#include <portable-file-dialogs.h> #include <filesystem> +#include <memory> namespace fs = std::filesystem; namespace { void LoadProjectAt(const std::filesystem::path& path) { auto& uis = UIState::GetInstance(); - auto& gs = GlobalStates::GetInstance(); - auto project = Project::Load(path); - auto uptr = std::unique_ptr<Project>(new Project(std::move(project))); - uis.SetCurrentProject(std::move(uptr)); + uis.SetCurrentProject(std::make_unique<Project>(std::move(project))); } void ProjectTab_Normal() { - // TODO + auto ls = LocaleStrings::Instance.get(); + auto& gs = GlobalStates::GetInstance(); + auto& uis = UIState::GetInstance(); + + if (ImGui::Button(ls->CloseActiveProject.Get())) { + uis.CloseCurrentProject(); + return; + } + ImGui::SameLine(); + if (ImGui::Button(ls->OpenActiveProjectInFileSystem.Get())) { + // TODO + } + + ImGui::Text("%s%s", ls->ActiveProjectName.Get(), uis.CurrentProject->GetName().c_str()); + ImGui::Text("%s%s", ls->ActiveProjectPath.Get(), uis.CurrentProject->GetPathString().c_str()); } void ProjectTab_NoProject() { @@ -42,6 +52,7 @@ void ProjectTab_NoProject() { auto TrySelectPath = [&](fs::path newPath) { if (fs::exists(newPath)) { dirNameIsValid = true; + dirName = newPath.string(); dirPath = std::move(newPath); } else { dirNameIsValid = false; @@ -49,15 +60,14 @@ void ProjectTab_NoProject() { }; if (ImGui::Button(ls->NewProject.Get())) { - auto vs = ImGui::GetMainViewport()->Size; // Viewport Size - ImGui::SetNextWindowSize({ vs.x * 0.5f, vs.y * 0.5f }); - ImGui::SetNextWindowPos({ vs.x / 2, vs.y / 2 }, ImGuiCond_Always, { 0.5f, 0.5f }); // Center window initially - ImGui::OpenPopup(ls->NewProjectTitle.Get()); + ImGui::SetNextWindowCentered(); + ImGui::SetNextWindowSizeRelScreen(0.5f, 0.5f); + ImGui::OpenPopup(ls->NewProjectDialogTitle.Get()); } // Make it so that the modal dialog has a close button bool newProjectDialogDummyTrue = true; - if (ImGui::BeginPopupModal(ls->NewProjectTitle.Get(), &newProjectDialogDummyTrue)) { + if (ImGui::BeginPopupModal(ls->NewProjectDialogTitle.Get(), &newProjectDialogDummyTrue)) { ImGui::InputTextWithHint("##ProjectName", ls->NewProjectNameHint.Get(), &projectName); if (ImGui::InputTextWithHint("##ProjectPath", ls->NewProjectPathHint.Get(), &dirName)) { @@ -76,30 +86,26 @@ void ProjectTab_NoProject() { ImGui::ErrorIcon(); ImGui::SameLine(); - ImGui::Text(ls->NewProjectEmptyNameError.Get()); + ImGui::Text("%s", ls->NewProjectEmptyNameError.Get()); } if (!dirNameIsValid) { ImGui::ErrorIcon(); ImGui::SameLine(); - ImGui::Text(ls->NewProjectInvalidPathError.Get()); + ImGui::Text("%s", ls->NewProjectInvalidPathError.Get()); } ImGui::Spacing(); bool formValid = dirNameIsValid && !projectName.empty(); - if (!formValid) { - ImGui::PushItemFlag(ImGuiItemFlags_Disabled, false); - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.5f * ImGui::GetStyle().Alpha); - } - if (ImGui::Button(ls->ConfirmNewProject.Get())) { + if (!formValid) ImGui::PushDisabled(); + if (ImGui::Button(ls->DialogConfirm.Get())) { ImGui::CloseCurrentPopup(); auto project = Project::Create(std::move(projectName), dirPath); - auto uptr = std::unique_ptr<Project>(new Project(std::move(project))); - uis.SetCurrentProject(std::move(uptr)); + uis.SetCurrentProject(std::make_unique<Project>(std::move(project))); // Dialog just got closed, reset states projectName = ""; @@ -107,13 +113,10 @@ void ProjectTab_NoProject() { dirPath = fs::path{}; dirNameIsValid = false; } - if (!formValid) { - ImGui::PopItemFlag(); - ImGui::PopStyleVar(); - } + if (!formValid) ImGui::PopDisabled(); ImGui::SameLine(); - if (ImGui::Button(ls->CancelNewProject.Get())) { + if (ImGui::Button(ls->DialogCancel.Get())) { ImGui::CloseCurrentPopup(); } @@ -129,7 +132,7 @@ void ProjectTab_NoProject() { } ImGui::Separator(); - ImGui::Text(ls->RecentProjects.Get()); + ImGui::Text("%s", ls->RecentProjects.Get()); ImGui::SameLine(); if (ImGui::Button(ls->ClearRecentProjects.Get())) { gs.ClearRecentProjects(); @@ -137,18 +140,18 @@ void ProjectTab_NoProject() { auto& recentProjects = gs.GetRecentProjects(); if (recentProjects.empty()) { - ImGui::Text(ls->NoRecentProjectsMessage.Get()); + ImGui::Text("%s", ls->NoRecentProjectsMessage.Get()); } for (auto it = recentProjects.begin(); it != recentProjects.end(); ++it) { auto& [path, recent] = *it; - ImGui::Text(recent.c_str()); + ImGui::Text("%s", recent.c_str()); ImGui::SameLine(); if (ImGui::Button(ICON_FA_EDIT)) { LoadProjectAt(path); } if (ImGui::IsItemHovered()) { - ImGui::SetTooltip(ls->OpenRecentProjectTooltip.Get()); + ImGui::SetTooltip("%s", ls->OpenRecentProjectTooltip.Get()); } ImGui::SameLine(); @@ -156,7 +159,7 @@ void ProjectTab_NoProject() { gs.RemoveRecentProject(std::distance(recentProjects.begin(), it)); } if (ImGui::IsItemHovered()) { - ImGui::SetTooltip(ls->DeleteRecentProjectTooltip.Get()); + ImGui::SetTooltip("%s", ls->DeleteRecentProjectTooltip.Get()); } } } diff --git a/core/src/UI/UI_Utils.cpp b/core/src/UI/UI_Utils.cpp index 61a62f0..615caae 100644 --- a/core/src/UI/UI_Utils.cpp +++ b/core/src/UI/UI_Utils.cpp @@ -2,6 +2,27 @@ #include <IconsFontAwesome.h> #include <imgui.h> +#include <imgui_internal.h> + +void ImGui::SetNextWindowSizeRelScreen(float xPercent, float yPercent, ImGuiCond_ cond) { + auto vs = ImGui::GetMainViewport()->Size; + ImGui::SetNextWindowSize({ vs.x * xPercent, vs.y * yPercent }, cond); +} + +void ImGui::SetNextWindowCentered(ImGuiCond_ cond) { + auto vs = ImGui::GetMainViewport()->Size; + ImGui::SetNextWindowPos({ vs.x / 2, vs.y / 2 }, cond, { 0.5f, 0.5f }); +} + +void ImGui::PushDisabled() { + ImGui::PushItemFlag(ImGuiItemFlags_Disabled, false); + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.5f * ImGui::GetStyle().Alpha); +} + +void ImGui::PopDisabled() { + ImGui::PopItemFlag(); + ImGui::PopStyleVar(); +} void ImGui::ErrorIcon() { ImGui::PushStyleColor(ImGuiCol_Text, ImVec4{ 237 / 255.0f, 67 / 255.0f, 55 / 255.0f, 1.0f }); // #ED4337 |