diff options
author | rtk0c <[email protected]> | 2022-06-27 18:27:13 -0700 |
---|---|---|
committer | rtk0c <[email protected]> | 2022-06-27 18:27:13 -0700 |
commit | 8f0dda5eab181b0f14f2652b4e984aaaae3f258c (patch) | |
tree | 4b825dc642cb6eb9a060e54bf8d69288fbee4904 /core/src/UI | |
parent | fad6a88a13ab1f888ab25ad0aae19c1d63aa0623 (diff) |
Start from a clean slate
Diffstat (limited to 'core/src/UI')
-rw-r--r-- | core/src/UI/UI.hpp | 48 | ||||
-rw-r--r-- | core/src/UI/UI_DatabaseView.cpp | 668 | ||||
-rw-r--r-- | core/src/UI/UI_Items.cpp | 252 | ||||
-rw-r--r-- | core/src/UI/UI_MainWindow.cpp | 237 | ||||
-rw-r--r-- | core/src/UI/UI_Settings.cpp | 8 | ||||
-rw-r--r-- | core/src/UI/UI_Templates.cpp | 977 | ||||
-rw-r--r-- | core/src/UI/UI_Utils.cpp | 315 | ||||
-rw-r--r-- | core/src/UI/UI_Workflows.cpp | 293 | ||||
-rw-r--r-- | core/src/UI/fwd.hpp | 6 |
9 files changed, 0 insertions, 2804 deletions
diff --git a/core/src/UI/UI.hpp b/core/src/UI/UI.hpp deleted file mode 100644 index 0a80b4c..0000000 --- a/core/src/UI/UI.hpp +++ /dev/null @@ -1,48 +0,0 @@ -#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(); - -bool Button(const char* label, bool disabled); -bool Button(const char* label, const ImVec2& sizeArg, bool disabled); - -void ErrorIcon(); -void ErrorMessage(const char* fmt, ...); -void WarningIcon(); -void WarningMessage(const char* fmt, ...); - -enum class IconType -{ - Flow, - Circle, - Square, - Grid, - RoundSquare, - Diamond, -}; - -void DrawIcon(ImDrawList* drawList, const ImVec2& a, const ImVec2& b, IconType type, bool filled, ImU32 color, ImU32 innerColor); -void Icon(const ImVec2& size, IconType type, bool filled, const ImVec4& color = ImVec4(1, 1, 1, 1), const ImVec4& innerColor = ImVec4(0, 0, 0, 0)); - -bool Splitter(bool splitVertically, float thickness, float* size1, float* size2, float minSize1, float minSize2, float splitterLongAxisSize = -1.0f); - -} // namespace ImGui - -namespace UI { - -void MainWindow(); - -void SettingsTab(); -void DatabaseViewTab(); -void ItemsTab(); -void WorkflowsTab(); -void TemplatesTab(); - -} // namespace UI diff --git a/core/src/UI/UI_DatabaseView.cpp b/core/src/UI/UI_DatabaseView.cpp deleted file mode 100644 index e128a59..0000000 --- a/core/src/UI/UI_DatabaseView.cpp +++ /dev/null @@ -1,668 +0,0 @@ -#include "UI.hpp" - -#include "Model/Filter.hpp" -#include "Model/GlobalStates.hpp" -#include "Model/Project.hpp" -#include "Utils/I18n.hpp" -#include "Utils/ScopeGuard.hpp" -#include "Utils/Time.hpp" - -#include <IconsFontAwesome.h> -#include <SQLiteCpp/Statement.h> -#include <imgui.h> -#include <tsl/robin_map.h> -#include <cstdint> -#include <iostream> -#include <memory> -#include <vector> - -namespace CPLT_UNITY_ID { - -// TODO move to Settings -constexpr int kMaxEntriesPerPage = 32; -constexpr int kSummaryItemCount = 3; -constexpr int kSummaryMaxLength = 25; - -std::pair<int, int> SplitEntryIndex(int entryIdx) -{ - int page = entryIdx / kMaxEntriesPerPage; - int row = entryIdx % kMaxEntriesPerPage; - return { page, row }; -} - -enum class DeliveryDirection -{ - FactoryToWarehouse, - WarehouseToCustomer, -}; - -struct Item -{ - int ItemId; - int Count; -}; - -struct DeliveryEntry -{ - std::vector<Item> Items; - std::string ItemsSummary; - std::string ShipmentTime; - std::string ArriveTime; - DeliveryDirection Direction; - - const char* StringifyDirection() const - { - switch (Direction) { - case DeliveryDirection::FactoryToWarehouse: return "Factory to warehouse"; - case DeliveryDirection::WarehouseToCustomer: return "Warehouse to customer"; - } - } -}; - -struct SaleEntry -{ - static constexpr auto kType = DeliveryDirection::WarehouseToCustomer; - - std::vector<DeliveryEntry> AssociatedDeliveries; - std::vector<Item> Items; - std::string ItemsSummary; - std::string Customer; - std::string Deadline; - std::string DeliveryTime; - int Id; - bool DeliveriesCached = false; -}; - -struct PurchaseEntry -{ - static constexpr auto kType = DeliveryDirection::FactoryToWarehouse; - - std::vector<DeliveryEntry> AssociatedDeliveries; - std::vector<Item> Items; - std::string ItemsSummary; - std::string Factory; - std::string OrderTime; - std::string DeliveryTime; - int Id; - bool DeliveriesCached; -}; - -template <class T> -class GenericTableView -{ -public: - // clang-format off - static constexpr bool kHasItems = requires(T t) - { - t.Items; - t.ItemsSummary; - }; - static constexpr bool kHasCustomer = requires(T t) { t.Customer; }; - static constexpr bool kHasDeadline = requires(T t) { t.Deadline; }; - static constexpr bool kHasFactory = requires(T t) { t.Factory; }; - static constexpr bool kHasOrderTime = requires(T t) { t.OrderTime; }; - static constexpr bool kHasCompletionTime = requires(T t) { t.DeliveryTime; }; - static constexpr int kColumnCount = kHasItems + kHasCustomer + kHasDeadline + kHasFactory + kHasOrderTime + kHasCompletionTime; - // clang-format on - - using Page = std::vector<T>; - - struct QueryStatements - { - SQLite::Statement* GetRowCount; - SQLite::Statement* GetRows; - SQLite::Statement* GetItems; - SQLite::Statement* FilterRows; - } Statements; - -protected: - // Translation entries for implementer to fill out - const char* mEditDialogTitle; - - Project* mProject; - Page* mCurrentPage = nullptr; - - /// Current active filter object, or \c nullptr. - std::unique_ptr<TableRowsFilter> mActiveFilter; - - tsl::robin_map<int, Page> mPages; - - /// A vector of entry indices (in \c mEntries) that are visible under the current filter. - std::vector<int> mActiveEntries; - - /// Number of rows in the table. - int mRowCount; - /// Last possible page for the current set table and filter (inclusive). - int mLastPage; - - /// The current page the user is on. - int mCurrentPageNumber; - - /// Row index of the select entry - int mSelectRow; - -public: - /// Calculate the first visible row's entry index. - int GetFirstVisibleRowIdx() const - { - return mCurrentPageNumber * kMaxEntriesPerPage; - } - - Project* GetProject() const - { - return mProject; - } - - void OnProjectChanged(Project* newProject) - { - mProject = newProject; - - auto& stmt = *Statements.GetRowCount; - if (stmt.executeStep()) { - mRowCount = stmt.getColumn(0).getInt(); - } else { - std::cerr << "Failed to fetch row count from SQLite.\n"; - mRowCount = 0; - } - - mActiveFilter = nullptr; - mActiveEntries.clear(); - - mPages.clear(); - mCurrentPage = nullptr; - UpdateLastPage(); - SetPage(0); - - mSelectRow = -1; - } - - TableRowsFilter* GetFilter() const - { - return mActiveFilter.get(); - } - - void OnFilterChanged() - { - auto& stmt = *Statements.FilterRows; - // clang-format off - DEFER { stmt.reset(); }; - // clang-format on - - // TODO lazy loading when too many results - mActiveEntries.clear(); - int columnIdx = stmt.getColumnIndex("Id"); - while (stmt.executeStep()) { - mActiveEntries.push_back(stmt.getColumn(columnIdx).getInt()); - } - - UpdateLastPage(); - SetPage(0); - - mSelectRow = -1; - } - - void OnFilterChanged(std::unique_ptr<TableRowsFilter> filter) - { - mActiveFilter = std::move(filter); - OnFilterChanged(); - } - - void Display() - { - bool dummy = true; - - if (ImGui::Button(ICON_FA_ARROW_LEFT, mCurrentPageNumber == 0)) { - SetPage(mCurrentPageNumber - 1); - } - - ImGui::SameLine(); - // +1 to convert from 0-based indices to 1-based, for human legibility - ImGui::Text("%d/%d", mCurrentPageNumber + 1, mLastPage + 1); - - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_ARROW_RIGHT, mCurrentPageNumber == mLastPage)) { - SetPage(mCurrentPageNumber + 1); - } - - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_PLUS " " I18N_TEXT("Add", L10N_ADD))) { - // TODO - } - - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_EDIT " " I18N_TEXT("Edit", L10N_EDIT), mSelectRow == -1)) { - ImGui::OpenPopup(mEditDialogTitle); - } - if (ImGui::BeginPopupModal(mEditDialogTitle, &dummy, ImGuiWindowFlags_AlwaysAutoResize)) { - auto& entry = (*mCurrentPage)[mSelectRow]; - int entryIdx = GetFirstVisibleRowIdx() + mSelectRow; - EditEntry(entry, entryIdx, mSelectRow); - ImGui::EndPopup(); - } - - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_TRASH " " I18N_TEXT("Delete", L10N_DELETE), mSelectRow == -1)) { - // TODO - } - - ImGui::Columns(2); - { - DisplayMainTable(); - ImGui::NextColumn(); - - if (mSelectRow == -1) { - ImGui::TextWrapped("%s", I18N_TEXT("Select an entry to show associated deliveries", L10N_DATABASE_MESSAGE_NO_ORDER_SELECTED)); - } else { - DisplayDeliveriesTable(); - } - ImGui::NextColumn(); - } - ImGui::Columns(1); - } - - void SetPage(int page) - { - mCurrentPageNumber = page; - mCurrentPage = &LoadAndGetPage(page); - mSelectRow = -1; - } - -private: - static int CalcPageForRowId(int64_t entryIdx) - { - return entryIdx / kMaxEntriesPerPage; - } - - /// Calculate range [begin, end) of index for the list of entries that are currently visible that the path-th page would show. - /// i.e. when there is a filter, look into \c mActiveEntryIndices; when there is no filter, use directly. - static std::pair<int64_t, int64_t> CalcRangeForPage(int page) - { - int begin = page * kMaxEntriesPerPage; - return { begin, begin + kMaxEntriesPerPage }; - } - - void DisplayMainTable() - { - if (ImGui::BeginTable("DataTable", kColumnCount, ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollX)) { - - if constexpr (kHasCustomer) ImGui::TableSetupColumn(I18N_TEXT("Customer", L10N_DATABASE_COLUMN_CUSTOMER)); - if constexpr (kHasDeadline) ImGui::TableSetupColumn(I18N_TEXT("Deadline", L10N_DATABASE_COLUMN_DEADLINE)); - if constexpr (kHasFactory) ImGui::TableSetupColumn(I18N_TEXT("Factory", L10N_DATABASE_COLUMN_FACTORY)); - if constexpr (kHasOrderTime) ImGui::TableSetupColumn(I18N_TEXT("Order time", L10N_DATABASE_COLUMN_ORDER_TIME)); - if constexpr (kHasCompletionTime) ImGui::TableSetupColumn(I18N_TEXT("Completion time", L10N_DATABASE_COLUMN_DELIVERY_TIME)); - if constexpr (kHasItems) ImGui::TableSetupColumn(I18N_TEXT("Items", L10N_DATABASE_COLUMN_ITEMS)); - ImGui::TableHeadersRow(); - - if (mActiveFilter) { - // TODO - auto [begin, end] = CalcRangeForPage(mCurrentPageNumber); - end = std::min(end, (int64_t)mActiveEntries.size() - 1); - for (int i = begin; i < end; ++i) { - int rowIdx = mActiveEntries[i]; - DisplayEntry(rowIdx); - } - } else { - int firstRowIdx = mCurrentPageNumber * kMaxEntriesPerPage; - auto& page = *mCurrentPage; - - int end = std::min((int)page.size(), kMaxEntriesPerPage); - for (int i = 0; i < end; ++i) { - DisplayEntry(page[i], i, firstRowIdx + i); - } - } - - ImGui::EndTable(); - } - } - - void DisplayEntry(int rowIdx) - { - // TODO - // auto [pageNumber, pageEntry] = SplitRowIndex(rowIdx); - // auto& entry = LoadAndGetPage(pageNumber)[pageEntry]; - // DisplayEntry(entry, rowIdx); - } - - void DisplayEntry(T& entry, int rowIdx, int entryIdx) - { - ImGui::PushID(rowIdx); - ImGui::TableNextRow(); - - if constexpr (kHasCustomer) { - ImGui::TableNextColumn(); - if (ImGui::Selectable(entry.Customer.c_str(), mSelectRow == rowIdx, ImGuiSelectableFlags_SpanAllColumns)) { - mSelectRow = rowIdx; - } - } - - if constexpr (kHasDeadline) { - ImGui::TableNextColumn(); - ImGui::TextUnformatted(entry.Deadline.c_str()); - } - - if constexpr (kHasFactory) { - ImGui::TableNextColumn(); - if (ImGui::Selectable(entry.Factory.c_str(), mSelectRow == rowIdx, ImGuiSelectableFlags_SpanAllColumns)) { - mSelectRow = rowIdx; - } - } - - if constexpr (kHasOrderTime) { - ImGui::TableNextColumn(); - ImGui::TextUnformatted(entry.OrderTime.c_str()); - } - - if constexpr (kHasCompletionTime) { - ImGui::TableNextColumn(); - if (entry.DeliveryTime.empty()) { - ImGui::TextUnformatted(I18N_TEXT("Not delivered", L10N_DATABASE_MESSAGE_NOT_DELIVERED)); - } else { - ImGui::TextUnformatted(entry.DeliveryTime.c_str()); - } - } - - if constexpr (kHasItems) { - ImGui::TableNextColumn(); - if (ImGui::TreeNode(entry.ItemsSummary.c_str())) { - DrawItems(entry.Items); - ImGui::TreePop(); - } - } - - ImGui::PopID(); - } - - void EditEntry(T& entry, int rowIdx, int entryIdx) - { - // TODO - } - - void DisplayDeliveriesTable() - { - if (ImGui::BeginTable("DeliveriesTable", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollX)) { - - ImGui::TableSetupColumn(I18N_TEXT("Shipment time", L10N_DATABASE_COLUMN_SHIPMENT_TIME)); - ImGui::TableSetupColumn(I18N_TEXT("Arrival time", L10N_DATABASE_COLUMN_ARRIVAL_TIME)); - ImGui::TableSetupColumn(I18N_TEXT("Items", L10N_DATABASE_COLUMN_ITEMS)); - ImGui::TableHeadersRow(); - - auto& entry = (*mCurrentPage)[mSelectRow]; - auto& deliveries = entry.AssociatedDeliveries; - for (auto& delivery : deliveries) { - ImGui::TableNextRow(); - - ImGui::TableNextColumn(); - ImGui::TextUnformatted(delivery.ShipmentTime.c_str()); - - ImGui::TableNextColumn(); - ImGui::TextUnformatted(delivery.ArriveTime.c_str()); - - ImGui::TableNextColumn(); - if (ImGui::TreeNode(delivery.ItemsSummary.c_str())) { - DrawItems(delivery.Items); - ImGui::TreePop(); - } - } - - ImGui::EndTable(); - } - } - - std::vector<Item> LoadItems(SQLite::Statement& stmt, int64_t id) - { - // clang-format off - DEFER { stmt.reset(); }; - // clang-format on - - stmt.bind(1, id); - - std::vector<Item> entries; - int itemIdCol = stmt.getColumnIndex("ItemId"); - int countCol = stmt.getColumnIndex("Count"); - while (stmt.executeStep()) { - entries.push_back(Item{ - .ItemId = stmt.getColumn(itemIdCol).getInt(), - .Count = stmt.getColumn(countCol).getInt(), - }); - } - - return entries; - } - - std::string CreateItemsSummary(const std::vector<Item>& items) - { - if (items.empty()) { - return "<empty>"; - } - - std::string result; - for (int i = 0, max = std::min((int)items.size(), kSummaryItemCount); i < max; ++i) { - auto& name = mProject->Products.Find(items[i].ItemId)->GetName(); - if (result.length() + name.length() > kSummaryMaxLength) { - break; - } - - result += name; - result += ", "; - } - - // Remove ", " - result.pop_back(); - result.pop_back(); - - result += "..."; - - return result; - } - - std::vector<DeliveryEntry> LoadDeliveriesEntries(int64_t orderId, DeliveryDirection type) - { - bool outgoingFlag; - switch (type) { - case DeliveryDirection::FactoryToWarehouse: outgoingFlag = false; break; - case DeliveryDirection::WarehouseToCustomer: outgoingFlag = true; break; - } - - auto& stmt = mProject->Database.GetDeliveries().FilterByTypeAndId; - // clang-format off - DEFER { stmt.reset(); }; - // clang-format on - - stmt.bind(1, orderId); - stmt.bind(2, outgoingFlag); - - std::vector<DeliveryEntry> entries; - int rowIdCol = stmt.getColumnIndex("Id"); - int sendTimeCol = stmt.getColumnIndex("ShipmentTime"); - int arrivalTimeCol = stmt.getColumnIndex("ArrivalTime"); - while (stmt.executeStep()) { - auto items = LoadItems( - mProject->Database.GetDeliveries().GetItems, - stmt.getColumn(rowIdCol).getInt64()); - auto summary = CreateItemsSummary(items); - - entries.push_back(DeliveryEntry{ - .Items = std::move(items), - .ItemsSummary = std::move(summary), - .ShipmentTime = TimeUtils::StringifyTimeStamp(stmt.getColumn(arrivalTimeCol).getInt64()), - .ArriveTime = TimeUtils::StringifyTimeStamp(stmt.getColumn(sendTimeCol).getInt64()), - .Direction = type, - }); - } - - return entries; - } - - Page& LoadAndGetPage(int page) - { - auto iter = mPages.find(page); - if (iter != mPages.end()) { - return iter.value(); - } - - auto& stmt = *Statements.GetRows; - // clang-format off - DEFER { stmt.reset(); }; - // clang-format on - - stmt.bind(1, kMaxEntriesPerPage); - stmt.bind(2, page * kMaxEntriesPerPage); - - // If a field flag is false, the column index won't be used (controlled by other if constexpr's downstream) - // so there is no UB here - - // This column is always necessary (and present) because the deliveries table require it - int idCol = stmt.getColumnIndex("Id"); - - int customerCol; - if constexpr (kHasCustomer) customerCol = stmt.getColumnIndex("Customer"); - - int deadlineCol; - if constexpr (kHasDeadline) deadlineCol = stmt.getColumnIndex("Deadline"); - - int factoryCol; - if constexpr (kHasFactory) factoryCol = stmt.getColumnIndex("Factory"); - - int orderTimeCol; - if constexpr (kHasOrderTime) orderTimeCol = stmt.getColumnIndex("OrderTime"); - - int deliveryTimeCol; - if constexpr (kHasCompletionTime) deliveryTimeCol = stmt.getColumnIndex("DeliveryTime"); - - Page entries; - while (stmt.executeStep()) { - auto& entry = entries.emplace_back(); - - auto id = stmt.getColumn(idCol).getInt64(); - entry.AssociatedDeliveries = LoadDeliveriesEntries(id, T::kType); - - if constexpr (kHasItems) { - auto items = LoadItems( - *Statements.GetItems, - id); - auto itemsSummary = CreateItemsSummary(items); - entry.Items = std::move(items); - entry.ItemsSummary = std::move(itemsSummary); - } - - if constexpr (kHasCustomer) { - auto customerId = stmt.getColumn(customerCol).getInt(); - entry.Customer = mProject->Customers.Find(customerId)->GetName(); - } - - if constexpr (kHasDeadline) { - auto timeStamp = stmt.getColumn(deadlineCol).getInt64(); - entry.Deadline = TimeUtils::StringifyTimeStamp(timeStamp); - } - - if constexpr (kHasFactory) { - auto factoryId = stmt.getColumn(factoryCol).getInt(); - entry.Factory = mProject->Factories.Find(factoryId)->GetName(); - } - - if constexpr (kHasOrderTime) { - auto timeStamp = stmt.getColumn(orderTimeCol).getInt64(); - entry.OrderTime = TimeUtils::StringifyTimeStamp(timeStamp); - } - - if constexpr (kHasCompletionTime) { - auto timeStamp = stmt.getColumn(deliveryTimeCol).getInt64(); - entry.DeliveryTime = TimeUtils::StringifyTimeStamp(timeStamp); - } - } - - auto [res, _] = mPages.try_emplace(page, std::move(entries)); - return res.value(); - } - - void DrawItems(const std::vector<Item>& items) - { - for (auto& item : items) { - auto& name = mProject->Products.Find(item.ItemId)->GetName(); - ImGui::Text("%s × %d", name.c_str(), item.Count); - } - } - - void UpdateLastPage() - { - mLastPage = mActiveEntries.empty() - ? CalcPageForRowId(mRowCount) - : CalcPageForRowId(mActiveEntries.back()); - } -}; - -class SalesTableView : public GenericTableView<SaleEntry> -{ -public: - SalesTableView() - { - mEditDialogTitle = I18N_TEXT("Edit sales entry", L10N_DATABASE_SALES_VIEW_EDIT_DIALOG_TITLE); - } - -#pragma clang diagnostic push -#pragma ide diagnostic ignored "HidingNonVirtualFunction" - void OnProjectChanged(Project* newProject) - { - auto& table = newProject->Database.GetSales(); - Statements.GetRowCount = &table.GetRowCount; - Statements.GetRows = &table.GetRows; - Statements.GetItems = &table.GetItems; - // TODO - // stmts.FilterRowsStatement = ; - - GenericTableView<SaleEntry>::OnProjectChanged(newProject); - } -#pragma clang diagnostic pop -}; - -class PurchasesTableView : public GenericTableView<PurchaseEntry> -{ -public: - PurchasesTableView() - { - mEditDialogTitle = I18N_TEXT("Edit purchase entry", L10N_DATABASE_PURCHASES_VIEW_EDIT_DIALOG_TITLE); - } - -#pragma clang diagnostic push -#pragma ide diagnostic ignored "HidingNonVirtualFunction" - void OnProjectChanged(Project* newProject) - { - auto& table = newProject->Database.GetPurchases(); - Statements.GetRowCount = &table.GetRowCount; - Statements.GetRows = &table.GetRows; - Statements.GetItems = &table.GetItems; - // TODO - // stmts.FilterRowsStatement = ; - - GenericTableView<PurchaseEntry>::OnProjectChanged(newProject); - } -#pragma clang diagnostic pop -}; -} // namespace CPLT_UNITY_ID - -void UI::DatabaseViewTab() -{ - auto& gs = GlobalStates::GetInstance(); - - static Project* currentProject = nullptr; - static CPLT_UNITY_ID::SalesTableView sales; - static CPLT_UNITY_ID::PurchasesTableView purchases; - - if (currentProject != gs.GetCurrentProject()) { - currentProject = gs.GetCurrentProject(); - sales.OnProjectChanged(currentProject); - purchases.OnProjectChanged(currentProject); - } - - if (ImGui::BeginTabBar("DatabaseViewTabs")) { - if (ImGui::BeginTabItem(I18N_TEXT("Sales", L10N_DATABASE_SALES_VIEW_TAB_NAME))) { - sales.Display(); - ImGui::EndTabItem(); - } - if (ImGui::BeginTabItem(I18N_TEXT("Purchases", L10N_DATABASE_PURCHASES_VIEW_TAB_NAME))) { - purchases.Display(); - ImGui::EndTabItem(); - } - ImGui::EndTabBar(); - } -} diff --git a/core/src/UI/UI_Items.cpp b/core/src/UI/UI_Items.cpp deleted file mode 100644 index a33c61b..0000000 --- a/core/src/UI/UI_Items.cpp +++ /dev/null @@ -1,252 +0,0 @@ -#include "UI.hpp" - -#include "Model/GlobalStates.hpp" -#include "Model/Project.hpp" -#include "Utils/I18n.hpp" - -#include <IconsFontAwesome.h> -#include <imgui.h> -#include <imgui_stdlib.h> - -namespace CPLT_UNITY_ID { - -enum class ActionResult -{ - Confirmed, - Canceled, - Pending, -}; - -/// \param list Item list that the item is in. -/// \param item A non-null pointer to the currently being edited item. It should not change until this function returns a non-\c ActionResult::Pending value. -template <class T> -ActionResult ItemEditor(ItemList<T>& list, T* item) -{ - constexpr bool kHasDescription = requires(T t) - { - t.GetDescription(); - }; - constexpr bool kHasEmail = requires(T t) - { - t.GetEmail(); - }; - - static bool duplicateName = false; - - static std::string name; - static std::string description; - static std::string email; - if (name.empty()) { - name = item->GetName(); - if constexpr (kHasDescription) description = item->GetDescription(); - if constexpr (kHasEmail) email = item->GetEmail(); - } - - auto ClearStates = [&]() { - duplicateName = false; - name = {}; - description = {}; - }; - - if (ImGui::InputText(I18N_TEXT("Name", L10N_ITEM_COLUMN_NAME), &name)) { - duplicateName = name != item->GetName() && list.Find(name) != nullptr; - } - if constexpr (kHasDescription) ImGui::InputText(I18N_TEXT("Description", L10N_ITEM_COLUMN_DESCRIPTION), &description); - if constexpr (kHasEmail) ImGui::InputText(I18N_TEXT("Email", L10N_ITEM_COLUMN_EMAIL), &email); - - if (name.empty()) { - ImGui::ErrorMessage(I18N_TEXT("Name cannot be empty", L10N_EMPTY_NAME_ERROR)); - } - if (duplicateName) { - ImGui::ErrorMessage(I18N_TEXT("Duplicate name", L10N_DUPLICATE_NAME_ERROR)); - } - - // Return Value - auto rv = ActionResult::Pending; - - if (ImGui::Button(I18N_TEXT("Confirm", L10N_CONFIRM), name.empty() || duplicateName)) { - if (item->GetName() != name) { - item->SetName(std::move(name)); - } - if constexpr (kHasDescription) - if (item->GetDescription() != description) { - item->SetDescription(std::move(description)); - } - if constexpr (kHasEmail) - if (item->GetEmail() != email) { - item->SetEmail(std::move(email)); - } - - ImGui::CloseCurrentPopup(); - ClearStates(); - rv = ActionResult::Confirmed; - } - - ImGui::SameLine(); - if (ImGui::Button(I18N_TEXT("Cancel", L10N_CANCEL))) { - ImGui::CloseCurrentPopup(); - ClearStates(); - rv = ActionResult::Canceled; - } - - return rv; -} - -template <class T> -void ItemListEntries(ItemList<T>& list, int& selectedIdx) -{ - constexpr bool kHasDescription = requires(T t) - { - t.GetDescription(); - }; - constexpr bool kHasEmail = requires(T t) - { - t.GetEmail(); - }; - constexpr bool kHasStock = requires(T t) - { - t.GetPrice(); - }; - constexpr bool kHasPrice = requires(T t) - { - t.GetPrice(); - }; - constexpr int kColumns = 1 /* Name column */ + kHasDescription + kHasEmail + kHasStock + kHasPrice; - - if (ImGui::BeginTable("", kColumns, ImGuiTableFlags_Borders)) { - - ImGui::TableSetupColumn(I18N_TEXT("Name", L10N_ITEM_COLUMN_NAME)); - if constexpr (kHasDescription) ImGui::TableSetupColumn(I18N_TEXT("Description", L10N_ITEM_COLUMN_DESCRIPTION)); - if constexpr (kHasEmail) ImGui::TableSetupColumn(I18N_TEXT("Email", L10N_ITEM_COLUMN_EMAIL)); - if constexpr (kHasStock) ImGui::TableSetupColumn(I18N_TEXT("Stock", L10N_ITEM_COLUMN_STOCK)); - if constexpr (kHasPrice) ImGui::TableSetupColumn(I18N_TEXT("Price", L10N_ITEM_COLUMN_PRICE)); - ImGui::TableHeadersRow(); - - size_t idx = 0; - for (auto& entry : list) { - if (entry.IsInvalid()) { - continue; - } - - ImGui::TableNextRow(); - - ImGui::TableNextColumn(); - if (ImGui::Selectable(entry.GetName().c_str(), selectedIdx == idx, ImGuiSelectableFlags_SpanAllColumns)) { - selectedIdx = idx; - } - - if constexpr (kHasDescription) { - ImGui::TableNextColumn(); - ImGui::TextUnformatted(entry.GetDescription().c_str()); - } - - if constexpr (kHasEmail) { - ImGui::TableNextColumn(); - ImGui::TextUnformatted(entry.GetEmail().c_str()); - } - - if constexpr (kHasStock) { - ImGui::TableNextColumn(); - ImGui::Text("%d", entry.GetStock()); - } - - if constexpr (kHasPrice) { - ImGui::TableNextColumn(); - // TODO format in dollars - ImGui::Text("%d", entry.GetPrice()); - } - - idx++; - } - ImGui::EndTable(); - } -} - -template <class T> -void ItemListEditor(ItemList<T>& list) -{ - bool opened = true; - static int selectedIdx = -1; - static T* editingItem = nullptr; - - if (ImGui::Button(ICON_FA_PLUS " " I18N_TEXT("Add", L10N_ADD))) { - ImGui::SetNextWindowCentered(); - ImGui::OpenPopup(I18N_TEXT("Add item", L10N_ITEM_ADD_DIALOG_TITLE)); - - editingItem = &list.Insert(""); - } - if (ImGui::BeginPopupModal(I18N_TEXT("Add item", L10N_ITEM_ADD_DIALOG_TITLE), &opened, ImGuiWindowFlags_AlwaysAutoResize)) { - switch (ItemEditor(list, editingItem)) { - case ActionResult::Confirmed: - editingItem = nullptr; - break; - case ActionResult::Canceled: - list.Remove(editingItem->GetId()); - editingItem = nullptr; - break; - default: - break; - } - ImGui::EndPopup(); - } - - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_EDIT " " I18N_TEXT("Edit", L10N_EDIT), selectedIdx == -1)) { - ImGui::SetNextWindowCentered(); - ImGui::OpenPopup(I18N_TEXT("Edit item", L10N_ITEM_EDIT_DIALOG_TITLE)); - - editingItem = list.Find(selectedIdx); - } - if (ImGui::BeginPopupModal(I18N_TEXT("Edit item", L10N_ITEM_EDIT_DIALOG_TITLE), &opened, ImGuiWindowFlags_AlwaysAutoResize)) { - if (ItemEditor(list, editingItem) != ActionResult::Pending) { - editingItem = nullptr; - } - ImGui::EndPopup(); - } - - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_TRASH " " I18N_TEXT("Delete", L10N_DELETE), selectedIdx == -1)) { - ImGui::SetNextWindowCentered(); - ImGui::OpenPopup(I18N_TEXT("Delete item", L10N_ITEM_DELETE_DIALOG_TITLE)); - - list.Remove(selectedIdx); - } - if (ImGui::BeginPopupModal(I18N_TEXT("Delete item", L10N_ITEM_DELETE_DIALOG_TITLE), &opened, ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::TextUnformatted(I18N_TEXT("Are you sure you want to delete this item?", L10N_ITEM_DELETE_DIALOG_MESSAGE)); - - if (ImGui::Button(I18N_TEXT("Confirm", L10N_CONFIRM))) { - ImGui::CloseCurrentPopup(); - } - - ImGui::SameLine(); - if (ImGui::Button(I18N_TEXT("Cancel", L10N_CANCEL))) { - ImGui::CloseCurrentPopup(); - } - - ImGui::EndPopup(); - } - - ItemListEntries<T>(list, selectedIdx); -} -} // namespace CPLT_UNITY_ID - -void UI::ItemsTab() -{ - auto& gs = GlobalStates::GetInstance(); - - if (ImGui::BeginTabBar("ItemViewTabs")) { - if (ImGui::BeginTabItem(I18N_TEXT("Products", L10N_ITEM_CATEGORY_PRODUCT))) { - CPLT_UNITY_ID::ItemListEditor(gs.GetCurrentProject()->Products); - ImGui::EndTabItem(); - } - if (ImGui::BeginTabItem(I18N_TEXT("Factories", L10N_ITEM_CATEGORY_FACTORY))) { - CPLT_UNITY_ID::ItemListEditor(gs.GetCurrentProject()->Factories); - ImGui::EndTabItem(); - } - if (ImGui::BeginTabItem(I18N_TEXT("Customers", L10N_ITEM_CATEGORY_CUSTOMER))) { - CPLT_UNITY_ID::ItemListEditor(gs.GetCurrentProject()->Customers); - ImGui::EndTabItem(); - } - ImGui::EndTabBar(); - } -} diff --git a/core/src/UI/UI_MainWindow.cpp b/core/src/UI/UI_MainWindow.cpp deleted file mode 100644 index d059359..0000000 --- a/core/src/UI/UI_MainWindow.cpp +++ /dev/null @@ -1,237 +0,0 @@ -#include "UI.hpp" - -#include "Model/GlobalStates.hpp" -#include "Model/Project.hpp" -#include "Utils/I18n.hpp" - -#include <IconsFontAwesome.h> -#include <imgui.h> -#include <imgui_stdlib.h> -#include <portable-file-dialogs.h> -#include <filesystem> -#include <memory> - -namespace fs = std::filesystem; - -namespace CPLT_UNITY_ID { -void ProjectTab_Normal() -{ - auto& gs = GlobalStates::GetInstance(); - - if (ImGui::Button(ICON_FA_TIMES " " I18N_TEXT("Close", L10N_CLOSE))) { - gs.SetCurrentProject(nullptr); - return; - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_FOLDER " " I18N_TEXT("Open in filesystem", L10N_PROJECT_OPEN_IN_FILESYSTEM))) { - // TODO - } - - ImGui::Text("%s %s", I18N_TEXT("Project name", L10N_PROJECT_NAME), gs.GetCurrentProject()->GetName().c_str()); - ImGui::Text("%s %s", I18N_TEXT("Project path", L10N_PROJECT_PATH), gs.GetCurrentProject()->GetPathString().c_str()); -} - -void ProjectTab_NoProject() -{ - auto& gs = GlobalStates::GetInstance(); - - bool openedDummy = true; - bool openErrorDialog = false; - static std::string projectName; - static std::string dirName; - static fs::path dirPath; - static bool dirNameIsValid = false; - - auto TrySelectPath = [&](fs::path newPath) { - if (fs::exists(newPath)) { - dirNameIsValid = true; - dirName = newPath.string(); - dirPath = std::move(newPath); - } else { - dirNameIsValid = false; - } - }; - - if (ImGui::Button(I18N_TEXT("Create project....", L10N_PROJECT_NEW))) { - ImGui::SetNextWindowCentered(); - ImGui::OpenPopup(I18N_TEXT("Create project wizard", L10N_PROJECT_NEW_DIALOG_TITLE)); - } - - // Make it so that the modal dialog has a close button - if (ImGui::BeginPopupModal(I18N_TEXT("Create project wizard", L10N_PROJECT_NEW_DIALOG_TITLE), &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::InputTextWithHint("##ProjectName", I18N_TEXT("Project name", L10N_PROJECT_NAME), &projectName); - - if (ImGui::InputTextWithHint("##ProjectPath", I18N_TEXT("Project path", L10N_PROJECT_PATH), &dirName)) { - // Changed, validate value - TrySelectPath(fs::path(dirName)); - } - ImGui::SameLine(); - if (ImGui::Button("...")) { - auto selection = pfd::select_folder(I18N_TEXT("Project path", L10N_PROJECT_NEW_PATH_DIALOG_TITLE)).result(); - if (!selection.empty()) { - TrySelectPath(fs::path(selection)); - } - } - - if (projectName.empty()) { - ImGui::ErrorMessage("%s", I18N_TEXT("Name cannot be empty", L10N_EMPTY_NAME_ERROR)); - } - if (!dirNameIsValid) { - ImGui::ErrorMessage("%s", I18N_TEXT("Invalid path", L10N_INVALID_PATH_ERROR)); - } - - ImGui::Spacing(); - - if (ImGui::Button(I18N_TEXT("Confirm", L10N_CONFIRM), !dirNameIsValid || projectName.empty())) { - ImGui::CloseCurrentPopup(); - - gs.SetCurrentProject(std::make_unique<Project>(std::move(dirPath), std::move(projectName))); - - // Dialog just got closed, reset states - projectName.clear(); - dirName.clear(); - dirPath.clear(); - dirNameIsValid = false; - } - - ImGui::SameLine(); - if (ImGui::Button(I18N_TEXT("Cancel", L10N_CANCEL))) { - ImGui::CloseCurrentPopup(); - } - - ImGui::EndPopup(); - } - - ImGui::SameLine(); - if (ImGui::Button(I18N_TEXT("Open project...", L10N_PROJECT_OPEN))) { - auto selection = pfd::open_file(I18N_TEXT("Open project", L10N_PROJECT_OPEN_DIALOG_TITLE)).result(); - if (!selection.empty()) { - fs::path path(selection[0]); - - try { - // Project's constructor wants a path to directory containing cplt_project.json - gs.SetCurrentProject(std::make_unique<Project>(path.parent_path())); - openErrorDialog = false; - } catch (const std::exception& e) { - openErrorDialog = true; - } - } - } - - // TODO cleanup UI - // Recent projects - - ImGui::Separator(); - ImGui::TextUnformatted(I18N_TEXT("Recent projects", L10N_PROJECT_RECENTS)); - - ImGui::SameLine(); - if (ImGui::Button(I18N_TEXT("Clear", L10N_PROJECT_RECENTS_CLEAR))) { - gs.ClearRecentProjects(); - } - - auto& rp = gs.GetRecentProjects(); - // End of vector is the most recently used, so that appending has less overhead - size_t toRemoveIdx = rp.size(); - - if (rp.empty()) { - ImGui::TextUnformatted(I18N_TEXT("No recent projects", L10N_PROJECT_RECENTS_NONE_PRESENT)); - } else { - for (auto it = rp.rbegin(); it != rp.rend(); ++it) { - auto& [path, recent] = *it; - ImGui::TextUnformatted(recent.c_str()); - - size_t idx = std::distance(it, rp.rend()) - 1; - ImGui::PushID(idx); - - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_FOLDER_OPEN)) { - try { - gs.SetCurrentProject(std::make_unique<Project>(path)); - openErrorDialog = false; - } catch (const std::exception& e) { - openErrorDialog = true; - } - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip(I18N_TEXT("Open this project", L10N_PROJECT_RECENTS_OPEN_TOOLTIP)); - } - - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_TRASH)) { - toRemoveIdx = idx; - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip(I18N_TEXT("Delete this project from the Recent Projects list, the project itself will not be affected", L10N_PROJECT_RECENTS_DELETE_TOOLTIP)); - } - - ImGui::PopID(); - } - } - - if (toRemoveIdx != rp.size()) { - gs.RemoveRecentProject(toRemoveIdx); - } - - if (openErrorDialog) { - ImGui::SetNextWindowCentered(); - ImGui::OpenPopup(I18N_TEXT("Error", L10N_ERROR)); - } - if (ImGui::BeginPopupModal(I18N_TEXT("Error", L10N_ERROR), &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::ErrorMessage("%s", I18N_TEXT("Invalid project file", L10N_PROJECT_INVALID_PROJECT_FORMAT)); - ImGui::EndPopup(); - } -} -} // namespace CPLT_UNITY_ID - -void UI::MainWindow() -{ - auto& gs = GlobalStates::GetInstance(); - - auto windowSize = ImGui::GetMainViewport()->Size; - ImGui::SetNextWindowSize({ windowSize.x, windowSize.y }); - ImGui::SetNextWindowPos({ 0, 0 }); - ImGui::Begin("##MainWindow", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize); - if (ImGui::BeginTabBar("MainWindowTabs")) { - if (ImGui::BeginTabItem(ICON_FA_COGS " " I18N_TEXT("Settings", L10N_MAIN_TAB_SETTINGS))) { - UI::SettingsTab(); - ImGui::EndTabItem(); - } - - if (ImGui::BeginTabItem(ICON_FA_FILE " " I18N_TEXT("Project", L10N_MAIN_WINDOW_TAB_PROJECT), nullptr)) { - if (gs.HasCurrentProject()) { - CPLT_UNITY_ID::ProjectTab_Normal(); - } else { - CPLT_UNITY_ID::ProjectTab_NoProject(); - } - ImGui::EndTabItem(); - } - if (!gs.HasCurrentProject()) { - // No project open, simply skip all project specific tabs - goto endTab; - } - - if (ImGui::BeginTabItem(ICON_FA_DATABASE " " I18N_TEXT("Data", L10N_MAIN_WINDOW_TAB_DATABASE_VIEW))) { - UI::DatabaseViewTab(); - ImGui::EndTabItem(); - } - - if (ImGui::BeginTabItem(ICON_FA_BOX " " I18N_TEXT("Items", L10N_MAIN_WINDOW_TAB_ITEMS))) { - UI::ItemsTab(); - ImGui::EndTabItem(); - } - - if (ImGui::BeginTabItem(ICON_FA_SCROLL " " I18N_TEXT("Workflows", L10N_MAIN_WINDOW_TAB_WORKFLOWS))) { - UI::WorkflowsTab(); - ImGui::EndTabItem(); - } - - if (ImGui::BeginTabItem(ICON_FA_TABLE " " I18N_TEXT("Templates", L10N_MAIN_WINDOW_TAB_TEMPLATES))) { - UI::TemplatesTab(); - ImGui::EndTabItem(); - } - - endTab: - ImGui::EndTabBar(); - } - ImGui::End(); -} diff --git a/core/src/UI/UI_Settings.cpp b/core/src/UI/UI_Settings.cpp deleted file mode 100644 index 107b94c..0000000 --- a/core/src/UI/UI_Settings.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "UI/UI.hpp" - -#include <imgui.h> - -void UI::SettingsTab() -{ - // TODO -} diff --git a/core/src/UI/UI_Templates.cpp b/core/src/UI/UI_Templates.cpp deleted file mode 100644 index cd15cb7..0000000 --- a/core/src/UI/UI_Templates.cpp +++ /dev/null @@ -1,977 +0,0 @@ -#include "UI.hpp" - -#include "Model/GlobalStates.hpp" -#include "Model/Project.hpp" -#include "Model/Template/TableTemplate.hpp" -#include "Model/Template/TableTemplateIterator.hpp" -#include "Model/Template/Template.hpp" -#include "Utils/I18n.hpp" - -#include <IconsFontAwesome.h> -#include <imgui.h> -#include <imgui_extra_math.h> -#include <imgui_internal.h> -#include <imgui_stdlib.h> -#include <charconv> -#include <fstream> -#include <iostream> -#include <utility> -#include <variant> - -namespace CPLT_UNITY_ID { -class TemplateUI -{ -public: - static std::unique_ptr<TemplateUI> CreateByKind(std::unique_ptr<Template> tmpl); - static std::unique_ptr<TemplateUI> CreateByKind(Template::Kind kind); - - virtual ~TemplateUI() = default; - virtual void Display() = 0; - virtual void Close() = 0; -}; - -// Table template styles -constexpr ImU32 kSingleParamOutline = IM_COL32(255, 255, 0, 255); -constexpr ImU32 kArrayGroupOutline = IM_COL32(255, 0, 0, 255); - -class TableTemplateUI : public TemplateUI -{ -private: - std::unique_ptr<TableTemplate> mTable; - - struct UICell - { - bool Hovered = false; - bool Held = false; - bool Selected = false; - }; - std::vector<UICell> mUICells; - - struct UIArrayGroup - { - ImVec2 Pos; - ImVec2 Size; - }; - std::vector<UIArrayGroup> mUIArrayGroups; - - struct Sizer - { - bool Hovered = false; - bool Held = false; - }; - std::vector<Sizer> mRowSizers; - std::vector<Sizer> mColSizers; - - /* Selection range */ - Vec2i mSelectionTL; - Vec2i mSelectionBR; - - /* Selection states */ - - /// "CStates" stands for "Constant cell selection States" - struct CStates - { - }; - - /// "SStates" stands for "Singular parameter selection States". - struct SStates - { - std::string EditBuffer; - bool ErrorDuplicateVarName; - bool HasLeftAG; - bool HasRightAG; - }; - - /// "AStates" stands for "Array group parameter selection States". - struct AStates - { - std::string EditBuffer; - bool ErrorDuplicateVarName; - }; - - // "RStates" stands for "Range selection States". - struct RStates - { - }; - - union - { - // Initialize to this element - std::monostate mIdleState{}; - CStates mCS; - SStates mSS; - AStates mAS; - RStates mRS; - }; - - /* Table resizer dialog states */ - int mNewTableWidth; - int mNewTableHeight; - - /* Table states */ - enum EditMode - { - ModeEditing, - ModeColumnResizing, - ModeRowResizing, - }; - EditMode mMode = ModeEditing; - - float mStartDragDim; - /// Depending on row/column sizer being dragged, this will be the y/x coordinate - float mStartDragMouseCoordinate; - - bool mDirty = false; - bool mFirstDraw = true; - -public: - TableTemplateUI(std::unique_ptr<TableTemplate> table) - : mTable{ std::move(table) } - , mSelectionTL{ -1, -1 } - , mSelectionBR{ -1, -1 } - { - // TODO debug code - Resize(6, 5); - } - - ~TableTemplateUI() override - { - // We can't move this to be a destructor of the union - // because that way it would run after the destruction of mTable - if (!IsSelected()) { - // Case: mIdleState - // Noop - } else if (mSelectionTL == mSelectionBR) { - switch (mTable->GetCell(mSelectionTL).Type) { - case TableCell::ConstantCell: - // Case: mCS - // Noop - break; - - case TableCell::SingularParametricCell: - // Case: mSS - mSS.EditBuffer.std::string::~string(); - break; - - case TableCell::ArrayParametricCell: - // Case: mAS - mAS.EditBuffer.std::string::~string(); - break; - } - } else { - // Case: mRS - // Noop - } - } - - void Display() override - { - ImGui::Columns(2); - if (mFirstDraw) { - mFirstDraw = false; - ImGui::SetColumnWidth(0, ImGui::GetWindowWidth() * 0.15f); - } - - DisplayInspector(); - ImGui::NextColumn(); - - auto initialPos = ImGui::GetCursorPos(); - DisplayTable(); - DisplayTableResizers(initialPos); - ImGui::NextColumn(); - - ImGui::Columns(1); - } - - void Close() override - { - // TODO - } - - void Resize(int width, int height) - { - mTable->Resize(width, height); - mUICells.resize(width * height); - mUIArrayGroups.resize(mTable->GetArrayGroupCount()); - mRowSizers.resize(width); - mColSizers.resize(height); - - for (size_t i = 0; i < mUIArrayGroups.size(); ++i) { - auto& ag = mTable->GetArrayGroup(i); - auto& uag = mUIArrayGroups[i]; - - auto itemSpacing = ImGui::GetStyle().ItemSpacing; - uag.Pos.x = CalcTablePixelWidth() + itemSpacing.x; - uag.Pos.y = CalcTablePixelHeight() + itemSpacing.y; - - uag.Size.x = mTable->GetRowHeight(ag.Row); - uag.Size.y = 0; - for (int x = ag.LeftCell; x <= ag.RightCell; ++x) { - uag.Size.y += mTable->GetColumnWidth(x); - } - } - - mSelectionTL = { 0, 0 }; - mSelectionBR = { 0, 0 }; - } - -private: - void DisplayInspector() - { - bool openedDummy = true; - - // This is an id, no need to localize - if (ImGui::BeginTabBar("Inspector")) { - if (ImGui::BeginTabItem(I18N_TEXT("Cell", L10N_TABLE_CELL))) { - if (!IsSelected()) { - ImGui::Text(I18N_TEXT("Select a cell to edit", L10N_TABLE_CELL_SELECT_MSG)); - } else if (mSelectionTL == mSelectionBR) { - DisplayCellProperties(mSelectionTL); - } else { - DisplayRangeProperties(mSelectionTL, mSelectionBR); - } - ImGui::EndTabItem(); - } - - auto OpenPopup = [](const char* name) { - // Act as if ImGui::OpenPopup is executed in the previous id stack frame (tab bar level) - // Note: we can't simply use ImGui::GetItemID() here, because that would return the id of the ImGui::Button - auto tabBar = ImGui::GetCurrentContext()->CurrentTabBar; - auto id = tabBar->Tabs[tabBar->LastTabItemIdx].ID; - ImGui::PopID(); - ImGui::OpenPopup(name); - ImGui::PushOverrideID(id); - }; - if (ImGui::BeginTabItem(I18N_TEXT("Table", L10N_TABLE))) { - if (ImGui::Button(I18N_TEXT("Configure table properties...", L10N_TABLE_CONFIGURE_PROPERTIES))) { - mNewTableWidth = mTable->GetTableWidth(); - mNewTableHeight = mTable->GetTableHeight(); - OpenPopup(I18N_TEXT("Table properties", L10N_TABLE_PROPERTIES)); - } - - int mode = mMode; - ImGui::RadioButton(I18N_TEXT("Edit table", L10N_TABLE_EDIT_TABLE), &mode, ModeEditing); - ImGui::RadioButton(I18N_TEXT("Resize column widths", L10N_TABLE_EDIT_RESIZE_COLS), &mode, ModeColumnResizing); - ImGui::RadioButton(I18N_TEXT("Resize rows heights", L10N_TABLE_EDIT_RESIZE_ROWS), &mode, ModeRowResizing); - mMode = static_cast<EditMode>(mode); - - // Table contents - DisplayTableContents(); - - ImGui::EndTabItem(); - } - if (ImGui::BeginPopupModal(I18N_TEXT("Table properties", L10N_TABLE_PROPERTIES), &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { - DisplayTableProperties(); - ImGui::EndPopup(); - } - - ImGui::EndTabBar(); - } - } - - static char NthUppercaseLetter(int n) - { - return (char)((int)'A' + n); - } - - static void ExcelRow(int row, char* bufferBegin, char* bufferEnd) - { - auto res = std::to_chars(bufferBegin, bufferEnd, row); - if (res.ec != std::errc()) { - return; - } - } - - static char* ExcelColumn(int column, char* bufferBegin, char* bufferEnd) - { - // https://stackoverflow.com/a/182924/11323702 - - int dividend = column; - int modulo; - - char* writeHead = bufferEnd - 1; - *writeHead = '\0'; - --writeHead; - - while (dividend > 0) { - if (writeHead < bufferBegin) { - return nullptr; - } - - modulo = (dividend - 1) % 26; - - *writeHead = NthUppercaseLetter(modulo); - --writeHead; - - dividend = (dividend - modulo) / 26; - } - - // `writeHead` at this point would be a one-past-the-bufferEnd reverse iterator (i.e. one-past-the-(text)beginning in the bufferBegin) - // add 1 to get to the actual beginning of the text - return writeHead + 1; - } - - void DisplayCellProperties(Vec2i pos) - { - auto& cell = mTable->GetCell(pos); - auto& uiCell = mUICells[pos.y * mTable->GetTableWidth() + pos.x]; - - char colStr[8]; // 2147483647 -> FXSHRXW, len == 7, along with \0 - char* colBegin = ExcelColumn(pos.x + 1, std::begin(colStr), std::end(colStr)); - char rowStr[11]; // len(2147483647) == 10, along with \0 - ExcelRow(pos.y + 1, std::begin(rowStr), std::end(rowStr)); - ImGui::Text(I18N_TEXT("Location: %s%s", L10N_TABLE_CELL_POS), colBegin, rowStr); - - switch (cell.Type) { - case TableCell::ConstantCell: - ImGui::Text(I18N_TEXT("Type: Constant", L10N_TABLE_CELL_TYPE_CONST)); - break; - case TableCell::SingularParametricCell: - ImGui::Text(I18N_TEXT("Type: Single parameter", L10N_TABLE_CELL_TYPE_PARAM)); - break; - case TableCell::ArrayParametricCell: - ImGui::Text(I18N_TEXT("Type: Array group", L10N_TABLE_CELL_TYPE_CREATE_AG)); - break; - } - - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_EDIT)) { - ImGui::OpenPopup("ConvertCtxMenu"); - } - if (ImGui::BeginPopup("ConvertCtxMenu")) { - bool constantEnabled = cell.Type != TableCell::ConstantCell; - if (ImGui::MenuItem(I18N_TEXT("Convert to regular cell", L10N_TABLE_CELL_CONV_CONST), nullptr, false, constantEnabled)) { - mTable->SetCellType(pos, TableCell::ConstantCell); - ResetCS(); - } - - bool singleEnabled = cell.Type != TableCell::SingularParametricCell; - if (ImGui::MenuItem(I18N_TEXT("Convert to parameter cell", L10N_TABLE_CELL_CONV_PARAM), nullptr, false, singleEnabled)) { - mTable->SetCellType(pos, TableCell::SingularParametricCell); - ResetSS(pos); - } - - bool arrayEnabled = cell.Type != TableCell::ArrayParametricCell; - if (ImGui::MenuItem(I18N_TEXT("Add to a new array group", L10N_TABLE_CELL_CONV_CREATE_AG), nullptr, false, arrayEnabled)) { - mTable->AddArrayGroup(pos.y, pos.x, pos.x); // Use automatically generated name - ResetAS(pos); - } - - bool leftEnabled = mSS.HasLeftAG && arrayEnabled; - if (ImGui::MenuItem(I18N_TEXT("Add to the array group to the left", L10N_TABLE_CELL_CONV_ADD_AG_LEFT), nullptr, false, leftEnabled)) { - auto& leftCell = mTable->GetCell({ pos.x - 1, pos.y }); - mTable->ExtendArrayGroupRight(leftCell.DataId, 1); - ResetAS(pos); - } - - bool rightEnabled = mSS.HasRightAG && arrayEnabled; - if (ImGui::MenuItem(I18N_TEXT("Add to the array group to the right", L10N_TABLE_CELL_CONV_ADD_AG_RIGHT), nullptr, false, rightEnabled)) { - auto& rightCell = mTable->GetCell({ pos.x + 1, pos.y }); - mTable->ExtendArrayGroupLeft(rightCell.DataId, 1); - ResetAS(pos); - } - - ImGui::EndPopup(); - } - - ImGui::Spacing(); - - constexpr auto kLeft = I18N_TEXT("Left", L10N_TABLE_CELL_ALIGN_LEFT); - constexpr auto kCenter = I18N_TEXT("Center", L10N_TABLE_CELL_ALIGN_CENTER); - constexpr auto kRight = I18N_TEXT("Right", L10N_TABLE_CELL_ALIGN_RIGHT); - - const char* horizontalText; - switch (cell.HorizontalAlignment) { - case TableCell::AlignAxisMin: horizontalText = kLeft; break; - case TableCell::AlignCenter: horizontalText = kCenter; break; - case TableCell::AlignAxisMax: horizontalText = kRight; break; - } - - if (ImGui::BeginCombo(I18N_TEXT("Horizontal alignment", L10N_TABLE_CELL_HORIZONTAL_ALIGNMENT), horizontalText)) { - if (ImGui::Selectable(kLeft, cell.HorizontalAlignment == TableCell::AlignAxisMin)) { - cell.HorizontalAlignment = TableCell::AlignAxisMin; - } - if (ImGui::Selectable(kCenter, cell.HorizontalAlignment == TableCell::AlignCenter)) { - cell.HorizontalAlignment = TableCell::AlignCenter; - } - if (ImGui::Selectable(kRight, cell.HorizontalAlignment == TableCell::AlignAxisMax)) { - cell.HorizontalAlignment = TableCell::AlignAxisMax; - } - ImGui::EndCombo(); - } - - constexpr auto kTop = I18N_TEXT("Left", L10N_TABLE_CELL_ALIGN_TOP); - constexpr auto kMiddle = I18N_TEXT("Middle", L10N_TABLE_CELL_ALIGN_MIDDLE); - constexpr auto kBottom = I18N_TEXT("Right", L10N_TABLE_CELL_ALIGN_BOTTOM); - - const char* verticalText; - switch (cell.VerticalAlignment) { - case TableCell::AlignAxisMin: verticalText = kTop; break; - case TableCell::AlignCenter: verticalText = kMiddle; break; - case TableCell::AlignAxisMax: verticalText = kBottom; break; - } - - if (ImGui::BeginCombo(I18N_TEXT("Vertical alignment", L10N_TABLE_CELL_VERTICAL_ALIGNMENT), verticalText)) { - if (ImGui::Selectable(kTop, cell.VerticalAlignment == TableCell::AlignAxisMin)) { - cell.VerticalAlignment = TableCell::AlignAxisMin; - } - if (ImGui::Selectable(kMiddle, cell.VerticalAlignment == TableCell::AlignCenter)) { - cell.VerticalAlignment = TableCell::AlignCenter; - } - if (ImGui::Selectable(kBottom, cell.VerticalAlignment == TableCell::AlignAxisMax)) { - cell.VerticalAlignment = TableCell::AlignAxisMax; - } - ImGui::EndCombo(); - } - - switch (cell.Type) { - case TableCell::ConstantCell: - ImGui::InputText(I18N_TEXT("Content", L10N_TABLE_CELL_CONTENT), &cell.Content); - break; - - case TableCell::SingularParametricCell: - if (ImGui::InputText(I18N_TEXT("Variable name", L10N_TABLE_CELL_VAR_NAME), &mSS.EditBuffer)) { - // Sync name change to table - bool success = mTable->UpdateParameterName(cell.Content, mSS.EditBuffer); - if (success) { - // Flush name to display content - cell.Content = mSS.EditBuffer; - mSS.ErrorDuplicateVarName = false; - } else { - mSS.ErrorDuplicateVarName = true; - } - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip(I18N_TEXT("Name of the parameter link to this cell.", L10N_TABLE_CELL_VAR_TOOLTIP)); - } - - if (mSS.ErrorDuplicateVarName) { - ImGui::ErrorMessage(I18N_TEXT("Variable name duplicated.", L10N_TABLE_CELL_VAR_NAME_DUP)); - } - break; - - case TableCell::ArrayParametricCell: - if (ImGui::InputText(I18N_TEXT("Variable name", L10N_TABLE_CELL_VAR_NAME), &mAS.EditBuffer)) { - auto ag = mTable->GetArrayGroup(cell.DataId); - bool success = ag.UpdateCellName(cell.Content, mAS.EditBuffer); - if (success) { - cell.Content = mAS.EditBuffer; - mAS.ErrorDuplicateVarName = false; - } else { - mAS.ErrorDuplicateVarName = true; - } - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip(I18N_TEXT("Name of the parameter link to this cell; local within the array group.", L10N_TABLE_CELL_ARRAY_VAR_TOOLTIP)); - } - - if (mAS.ErrorDuplicateVarName) { - ImGui::ErrorMessage(I18N_TEXT("Variable name duplicated.", L10N_TABLE_CELL_VAR_NAME_DUP)); - } - break; - } - } - - void DisplayRangeProperties(Vec2i tl, Vec2i br) - { - // TODO - } - - void DisplayTableContents() - { - if (ImGui::TreeNode(ICON_FA_BONG " " I18N_TEXT("Parameters", L10N_TABLE_SINGLE_PARAMS))) { - TableSingleParamsIter iter(*mTable); - while (iter.HasNext()) { - auto& cell = iter.Next(); - if (ImGui::Selectable(cell.Content.c_str())) { - SelectCell(cell.Location); - } - } - ImGui::TreePop(); - } - if (ImGui::TreeNode(ICON_FA_LIST " " I18N_TEXT("Array groups", L10N_TABLE_ARRAY_GROUPS))) { - TableArrayGroupsIter iter(*mTable); - // For each array group - while (iter.HasNext()) { - if (ImGui::TreeNode(iter.PeekNameCStr())) { - auto& ag = iter.Peek(); - // For each cell in the array group - for (int x = ag.LeftCell; x <= ag.RightCell; ++x) { - Vec2i pos{ x, ag.Row }; - auto& cell = mTable->GetCell(pos); - if (ImGui::Selectable(cell.Content.c_str())) { - SelectCell(pos); - } - } - ImGui::TreePop(); - } - iter.Next(); - } - ImGui::TreePop(); - } - } - - void DisplayTableProperties() - { - ImGui::InputInt(I18N_TEXT("Width", L10N_TABLE_WIDTH), &mNewTableWidth); - ImGui::InputInt(I18N_TEXT("Height", L10N_TABLE_HEIGHT), &mNewTableHeight); - - if (ImGui::Button(I18N_TEXT("Confirm", L10N_CONFIRM))) { - ImGui::CloseCurrentPopup(); - Resize(mNewTableWidth, mNewTableHeight); - } - ImGui::SameLine(); - if (ImGui::Button(I18N_TEXT("Cancel", L10N_CANCEL))) { - ImGui::CloseCurrentPopup(); - } - - // TODO - } - - void DisplayTable() - { - struct CellPalette - { - ImU32 Regular; - ImU32 Hovered; - ImU32 Active; - - ImU32 GetColorFor(const UICell& cell) const - { - if (cell.Held) { - return Active; - } else if (cell.Hovered) { - return Hovered; - } else { - return Regular; - } - } - }; - - CellPalette constantPalette{ - .Regular = ImGui::GetColorU32(ImGuiCol_Button), - .Hovered = ImGui::GetColorU32(ImGuiCol_ButtonHovered), - .Active = ImGui::GetColorU32(ImGuiCol_ButtonActive), - }; - CellPalette paramPalette{ - .Regular = IM_COL32(0, 214, 4, 102), - .Hovered = IM_COL32(0, 214, 4, 255), - .Active = IM_COL32(0, 191, 2, 255), - }; - - // TODO array group color - - auto navHighlight = ImGui::GetColorU32(ImGuiCol_NavHighlight); - - int colCount = mTable->GetTableWidth(); - int rowCount = mTable->GetTableHeight(); - for (int rowIdx = 0; rowIdx < rowCount; ++rowIdx) { - int rowHeight = mTable->GetRowHeight(rowIdx); - - for (int colIdx = 0; colIdx < colCount; ++colIdx) { - int colWidth = mTable->GetColumnWidth(colIdx); - - int i = rowIdx * colCount + colIdx; - auto window = ImGui::GetCurrentWindow(); - auto id = window->GetID(i); - - Vec2i cellLoc{ colIdx, rowIdx }; - auto& cell = mTable->GetCell(cellLoc); - auto& uiCell = mUICells[i]; - - ImVec2 size(colWidth, rowHeight); - ImRect rect{ - window->DC.CursorPos, - window->DC.CursorPos + ImGui::CalcItemSize(size, 0.0f, 0.0f), - }; - - /* Draw cell selection */ - - if (uiCell.Selected) { - constexpr int mt = 2; // Marker Thickness - constexpr int ms = 8; // Marker Size - - ImVec2 outerTL(rect.Min - ImVec2(mt, mt)); - ImVec2 outerBR(rect.Max + ImVec2(mt, mt)); - - // Top left - window->DrawList->AddRectFilled(outerTL + ImVec2(0, 0), outerTL + ImVec2(ms, mt), navHighlight); - window->DrawList->AddRectFilled(outerTL + ImVec2(0, mt), outerTL + ImVec2(mt, ms), navHighlight); - - // Top right - ImVec2 outerTR(outerBR.x, outerTL.y); - window->DrawList->AddRectFilled(outerTR + ImVec2(-ms, 0), outerTR + ImVec2(0, mt), navHighlight); - window->DrawList->AddRectFilled(outerTR + ImVec2(-mt, mt), outerTR + ImVec2(0, ms), navHighlight); - - // Bottom right - window->DrawList->AddRectFilled(outerBR + ImVec2(-ms, -mt), outerBR + ImVec2(0, 0), navHighlight); - window->DrawList->AddRectFilled(outerBR + ImVec2(-mt, -ms), outerBR + ImVec2(0, -mt), navHighlight); - - // Bottom left - ImVec2 outerBL(outerTL.x, outerBR.y); - window->DrawList->AddRectFilled(outerBL + ImVec2(0, -mt), outerBL + ImVec2(ms, 0), navHighlight); - window->DrawList->AddRectFilled(outerBL + ImVec2(0, -ms), outerBL + ImVec2(mt, -mt), navHighlight); - } - - /* Draw cell body */ - - CellPalette* palette; - switch (cell.Type) { - case TableCell::ConstantCell: - palette = &constantPalette; - break; - - case TableCell::SingularParametricCell: - case TableCell::ArrayParametricCell: - palette = ¶mPalette; - break; - } - - window->DrawList->AddRectFilled(rect.Min, rect.Max, palette->GetColorFor(uiCell)); - - /* Draw cell content */ - - auto content = cell.Content.c_str(); - auto contentEnd = content + cell.Content.size(); - auto textSize = ImGui::CalcTextSize(content, contentEnd); - - ImVec2 textRenderPos; - switch (cell.HorizontalAlignment) { - case TableCell::AlignAxisMin: textRenderPos.x = rect.Min.x; break; - case TableCell::AlignCenter: textRenderPos.x = rect.Min.x + colWidth / 2 - textSize.x / 2; break; - case TableCell::AlignAxisMax: textRenderPos.x = rect.Max.x - textSize.x; break; - } - switch (cell.VerticalAlignment) { - case TableCell::AlignAxisMin: textRenderPos.y = rect.Min.y; break; - case TableCell::AlignCenter: textRenderPos.y = rect.Min.y + rowHeight / 2 - textSize.y / 2; break; - case TableCell::AlignAxisMax: textRenderPos.y = rect.Max.y - textSize.y; break; - } - window->DrawList->AddText(textRenderPos, IM_COL32(0, 0, 0, 255), content, contentEnd); - - /* Update ImGui cursor */ - - ImGui::ItemSize(size); - if (!ImGui::ItemAdd(rect, id)) { - goto logicEnd; - } - - if (mMode != ModeEditing) { - goto logicEnd; - } - if (ImGui::ButtonBehavior(rect, id, &uiCell.Hovered, &uiCell.Held)) { - if (ImGui::GetIO().KeyShift && IsSelected()) { - SelectRange(mSelectionTL, { colIdx, rowIdx }); - } else { - SelectCell({ colIdx, rowIdx }); - } - } - - logicEnd: - // Don't SameLine() if we are on the last cell in the row - if (colIdx != colCount - 1) { - ImGui::SameLine(); - } - } - } - - for (auto& uag : mUIArrayGroups) { - ImGui::GetCurrentWindow()->DrawList->AddRect( - uag.Pos - ImVec2(1, 1), - uag.Pos + uag.Size + ImVec2(1, 1), - kArrayGroupOutline); - } - } - - void DisplayResizers( - ImVec2 pos, - ImVec2 sizerDim, - ImVec2 sizerOffset, - std::type_identity_t<float ImVec2::*> vecCompGetter, - std::type_identity_t<int (TableTemplate::*)() const> lenGetter, - std::type_identity_t<int (TableTemplate::*)(int) const> dimGetter, - std::type_identity_t<void (TableTemplate::*)(int, int)> dimSetter) - { - auto window = ImGui::GetCurrentWindow(); - auto spacing = ImGui::GetStyle().ItemSpacing.*vecCompGetter; - - auto regularColor = ImGui::GetColorU32(ImGuiCol_Button); - auto hoveredColor = ImGui::GetColorU32(ImGuiCol_ButtonHovered); - auto activeColor = ImGui::GetColorU32(ImGuiCol_ButtonActive); - - auto GetColor = [&](const Sizer& sizer) -> ImU32 { - if (sizer.Held) { - return activeColor; - } else if (sizer.Hovered) { - return hoveredColor; - } else { - return regularColor; - } - }; - - for (int ix = 0; ix < (mTable.get()->*lenGetter)(); ++ix) { - // ImGui uses float for sizes, our table uses int (because excel uses int) - // Convert here to avoid mountains of narrowing warnings below - auto dim = (float)(mTable.get()->*dimGetter)(ix); - - pos.*vecCompGetter += dim; - ImRect rect{ - pos - sizerOffset, - pos - sizerOffset + ImGui::CalcItemSize(sizerDim, 0.0f, 0.0f), - }; - pos.*vecCompGetter += spacing; - - auto& sizer = mColSizers[ix]; - auto id = window->GetID(ix); - window->DrawList->AddRectFilled(rect.Min, rect.Max, GetColor(sizer)); - - if (ImGui::ButtonBehavior(rect, id, &sizer.Hovered, &sizer.Held, ImGuiButtonFlags_PressedOnClick)) { - mStartDragDim = dim; - mStartDragMouseCoordinate = ImGui::GetMousePos().*vecCompGetter; - } - if (sizer.Held) { - float change = ImGui::GetMousePos().*vecCompGetter - mStartDragMouseCoordinate; - float colWidth = std::max(mStartDragDim + change, 1.0f); - (mTable.get()->*dimSetter)(ix, (int)colWidth); - } - } - } - - void DisplayTableResizers(ImVec2 topLeftPixelPos) - { - constexpr float kExtraSideLength = 5.0f; - constexpr float kExtraAxialLength = 2.0f; - - switch (mMode) { - case ModeEditing: break; - - case ModeColumnResizing: - ImGui::PushID("Cols"); - DisplayResizers( - topLeftPixelPos, - ImVec2( - ImGui::GetStyle().ItemSpacing.x + kExtraSideLength * 2, - CalcTablePixelHeight() + kExtraAxialLength * 2), - ImVec2(kExtraSideLength, kExtraAxialLength), - &ImVec2::x, - &TableTemplate::GetTableWidth, - &TableTemplate::GetColumnWidth, - &TableTemplate::SetColumnWidth); - ImGui::PopID(); - break; - - case ModeRowResizing: - ImGui::PushID("Rows"); - DisplayResizers( - topLeftPixelPos, - ImVec2( - CalcTablePixelWidth() + kExtraAxialLength * 2, - ImGui::GetStyle().ItemSpacing.y + kExtraSideLength * 2), - ImVec2(kExtraAxialLength, kExtraSideLength), - &ImVec2::y, - &TableTemplate::GetTableHeight, - &TableTemplate::GetRowHeight, - &TableTemplate::SetRowHeight); - ImGui::PopID(); - break; - } - } - - float CalcTablePixelWidth() const - { - float horizontalSpacing = ImGui::GetStyle().ItemSpacing.x; - float width = 0; - for (int x = 0; x < mTable->GetTableWidth(); ++x) { - width += mTable->GetColumnWidth(x); - width += horizontalSpacing; - } - return width - horizontalSpacing; - } - - float CalcTablePixelHeight() const - { - float verticalSpacing = ImGui::GetStyle().ItemSpacing.y; - float height = 0; - for (int y = 0; y < mTable->GetTableHeight(); ++y) { - height += mTable->GetRowHeight(y); - height += verticalSpacing; - } - return height - verticalSpacing; - } - - template <class TFunction> - void ForeachSelectedCell(const TFunction& func) - { - for (int y = mSelectionTL.y; y <= mSelectionBR.y; ++y) { - for (int x = mSelectionTL.x; x <= mSelectionBR.x; ++x) { - int i = y * mTable->GetTableWidth() + x; - func(i, x, y); - } - } - } - - bool IsSelected() const - { - return mSelectionTL.x != -1; - } - - void ClearSelection() - { - if (IsSelected()) { - ForeachSelectedCell([this](int i, int, int) { - auto& uiCell = mUICells[i]; - uiCell.Selected = false; - }); - } - - mSelectionTL = { -1, -1 }; - mSelectionBR = { -1, -1 }; - - ResetIdleState(); - } - - void ResetIdleState() - { - mIdleState = {}; - } - - void SelectRange(Vec2i p1, Vec2i p2) - { - ClearSelection(); - - if (p2.x < p1.x) { - std::swap(p2.x, p1.x); - } - if (p2.y < p1.y) { - std::swap(p2.y, p1.y); - } - - mSelectionTL = p1; - mSelectionBR = p2; - - ForeachSelectedCell([this](int i, int, int) { - auto& uiCell = mUICells[i]; - uiCell.Selected = true; - }); - - ResetRS(); - } - - void ResetRS() - { - mRS = {}; - } - - void SelectCell(Vec2i pos) - { - ClearSelection(); - - mSelectionTL = pos; - mSelectionBR = pos; - - int i = pos.y * mTable->GetTableWidth() + pos.x; - mUICells[i].Selected = true; - - switch (mTable->GetCell(pos).Type) { - case TableCell::ConstantCell: ResetCS(); break; - case TableCell::SingularParametricCell: ResetSS(pos); break; - case TableCell::ArrayParametricCell: ResetAS(pos); break; - } - } - - void ResetCS() - { - mCS = {}; - } - - void ResetSS(Vec2i pos) - { - new (&mSS) SStates{ - .EditBuffer = mTable->GetCell(pos).Content, - .ErrorDuplicateVarName = false, - .HasLeftAG = pos.x > 1 && mTable->GetCell({ pos.x - 1, pos.y }).Type == TableCell::ArrayParametricCell, - .HasRightAG = pos.x < mTable->GetTableWidth() - 1 && mTable->GetCell({ pos.x + 1, pos.y }).Type == TableCell::ArrayParametricCell, - }; - } - - void ResetAS(Vec2i pos) - { - new (&mAS) AStates{ - .EditBuffer = mTable->GetCell(pos).Content, - .ErrorDuplicateVarName = false, - }; - } -}; - -template <class TTarget> -static auto CastTemplateAs(std::unique_ptr<Template>& input) requires std::is_base_of_v<Template, TTarget> -{ - return std::unique_ptr<TTarget>(static_cast<TTarget*>(input.release())); -} - -std::unique_ptr<TemplateUI> TemplateUI::CreateByKind(std::unique_ptr<Template> tmpl) -{ - switch (tmpl->GetKind()) { - case Template::KD_Table: return std::make_unique<TableTemplateUI>(CastTemplateAs<TableTemplate>(tmpl)); - case Template::InvalidKind: break; - } - return nullptr; -} - -std::unique_ptr<TemplateUI> TemplateUI::CreateByKind(Template::Kind kind) -{ - switch (kind) { - case Template::KD_Table: return std::make_unique<TableTemplateUI>(std::make_unique<TableTemplate>()); - case Template::InvalidKind: break; - } - return nullptr; -} -} // namespace CPLT_UNITY_ID - -void UI::TemplatesTab() -{ - auto& project = *GlobalStates::GetInstance().GetCurrentProject(); - - static std::unique_ptr<CPLT_UNITY_ID::TemplateUI> openTemplate; - static AssetList::ListState state; - bool openedDummy = true; - - // Toolbar item: close - if (ImGui::Button(ICON_FA_TIMES " " I18N_TEXT("Close", L10N_CLOSE), openTemplate == nullptr)) { - openTemplate->Close(); - openTemplate = nullptr; - } - - // Toolbar item: open... - ImGui::SameLine(); - if (ImGui::Button(I18N_TEXT("Open asset...", L10N_ASSET_OPEN))) { - ImGui::OpenPopup(I18N_TEXT("Open asset", L10N_ASSET_OPEN_DIALOG_TITLE)); - } - if (ImGui::BeginPopupModal(I18N_TEXT("Open asset", L10N_ASSET_OPEN_DIALOG_TITLE), &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { - if (ImGui::Button(ICON_FA_FOLDER_OPEN " " I18N_TEXT("Open", L10N_OPEN), state.SelectedAsset == nullptr)) { - ImGui::CloseCurrentPopup(); - - auto tmpl = project.Templates.Load(*state.SelectedAsset); - openTemplate = CPLT_UNITY_ID::TemplateUI::CreateByKind(std::move(tmpl)); - } - ImGui::SameLine(); - project.Templates.DisplayControls(state); - project.Templates.DisplayDetailsList(state); - - ImGui::EndPopup(); - } - - // Toolbar item: manage... - ImGui::SameLine(); - if (ImGui::Button(I18N_TEXT("Manage assets...", L10N_ASSET_MANAGE))) { - ImGui::OpenPopup(I18N_TEXT("Manage assets", L10N_ASSET_MANAGE_DIALOG_TITLE)); - } - if (ImGui::BeginPopupModal(I18N_TEXT("Manage assets", L10N_ASSET_MANAGE_DIALOG_TITLE), &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { - project.Templates.DisplayControls(state); - project.Templates.DisplayDetailsList(state); - ImGui::EndPopup(); - } - - if (openTemplate) { - openTemplate->Display(); - } -} diff --git a/core/src/UI/UI_Utils.cpp b/core/src/UI/UI_Utils.cpp deleted file mode 100644 index a2bf692..0000000 --- a/core/src/UI/UI_Utils.cpp +++ /dev/null @@ -1,315 +0,0 @@ -#include "UI.hpp" - -#include <IconsFontAwesome.h> -#include <imgui.h> - -#define IMGUI_DEFINE_MATH_OPERATORS -#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, true); - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.5f * ImGui::GetStyle().Alpha); -} - -void ImGui::PopDisabled() -{ - ImGui::PopItemFlag(); - ImGui::PopStyleVar(); -} - -bool ImGui::Button(const char* label, bool disabled) -{ - return Button(label, ImVec2{}, disabled); -} - -bool ImGui::Button(const char* label, const ImVec2& sizeArg, bool disabled) -{ - if (disabled) PushDisabled(); - bool res = ImGui::Button(label, sizeArg); - if (disabled) PopDisabled(); - - // Help clang-tidy's static analyzer: if the button is disabled, res should always be false - assert(!disabled || (disabled && !res)); - - return res; -} - -void ImGui::ErrorIcon() -{ - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4{ 237 / 255.0f, 67 / 255.0f, 55 / 255.0f, 1.0f }); // #ED4337 - ImGui::Text(ICON_FA_EXCLAMATION_CIRCLE); - ImGui::PopStyleColor(); -} - -void ImGui::ErrorMessage(const char* fmt, ...) -{ - ErrorIcon(); - SameLine(); - - va_list args; - va_start(args, fmt); - TextV(fmt, args); - va_end(args); -} - -void ImGui::WarningIcon() -{ - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4{ 255 / 255.0f, 184 / 255.0f, 24 / 255.0f, 1.0f }); // #FFB818 - ImGui::Text(ICON_FA_EXCLAMATION_TRIANGLE); - ImGui::PopStyleColor(); -} - -void ImGui::WarningMessage(const char* fmt, ...) -{ - WarningIcon(); - SameLine(); - - va_list args; - va_start(args, fmt); - TextV(fmt, args); - va_end(args); -} - -void ImGui::DrawIcon(ImDrawList* drawList, const ImVec2& a, const ImVec2& b, IconType type, bool filled, ImU32 color, ImU32 innerColor) -{ - // Taken from https://github.com/thedmd/imgui-node-editor/blob/master/examples/blueprints-example/utilities/drawing.cpp - // ax::NodeEditor::DrawIcon - - // Brace style was adapted but no names are changed - - auto rect = ImRect(a, b); - auto rect_x = rect.Min.x; - auto rect_y = rect.Min.y; - auto rect_w = rect.Max.x - rect.Min.x; - auto rect_h = rect.Max.y - rect.Min.y; - auto rect_center_x = (rect.Min.x + rect.Max.x) * 0.5f; - auto rect_center_y = (rect.Min.y + rect.Max.y) * 0.5f; - auto rect_center = ImVec2(rect_center_x, rect_center_y); - const auto outline_scale = rect_w / 24.0f; - const auto extra_segments = static_cast<int>(2 * outline_scale); // for full circle - - if (type == IconType::Flow) { - const auto origin_scale = rect_w / 24.0f; - - const auto offset_x = 1.0f * origin_scale; - const auto offset_y = 0.0f * origin_scale; - const auto margin = 2.0f * origin_scale; - const auto rounding = 0.1f * origin_scale; - const auto tip_round = 0.7f; // percentage of triangle edge (for tip) - //const auto edge_round = 0.7f; // percentage of triangle edge (for corner) - const auto canvas = ImRect( - rect.Min.x + margin + offset_x, - rect.Min.y + margin + offset_y, - rect.Max.x - margin + offset_x, - rect.Max.y - margin + offset_y); - const auto canvas_x = canvas.Min.x; - const auto canvas_y = canvas.Min.y; - const auto canvas_w = canvas.Max.x - canvas.Min.x; - const auto canvas_h = canvas.Max.y - canvas.Min.y; - - const auto left = canvas_x + canvas_w * 0.5f * 0.3f; - const auto right = canvas_x + canvas_w - canvas_w * 0.5f * 0.3f; - const auto top = canvas_y + canvas_h * 0.5f * 0.2f; - const auto bottom = canvas_y + canvas_h - canvas_h * 0.5f * 0.2f; - const auto center_y = (top + bottom) * 0.5f; - //const auto angle = AX_PI * 0.5f * 0.5f * 0.5f; - - const auto tip_top = ImVec2(canvas_x + canvas_w * 0.5f, top); - const auto tip_right = ImVec2(right, center_y); - const auto tip_bottom = ImVec2(canvas_x + canvas_w * 0.5f, bottom); - - drawList->PathLineTo(ImVec2(left, top) + ImVec2(0, rounding)); - drawList->PathBezierCurveTo( - ImVec2(left, top), - ImVec2(left, top), - ImVec2(left, top) + ImVec2(rounding, 0)); - drawList->PathLineTo(tip_top); - drawList->PathLineTo(tip_top + (tip_right - tip_top) * tip_round); - drawList->PathBezierCurveTo( - tip_right, - tip_right, - tip_bottom + (tip_right - tip_bottom) * tip_round); - drawList->PathLineTo(tip_bottom); - drawList->PathLineTo(ImVec2(left, bottom) + ImVec2(rounding, 0)); - drawList->PathBezierCurveTo( - ImVec2(left, bottom), - ImVec2(left, bottom), - ImVec2(left, bottom) - ImVec2(0, rounding)); - - if (!filled) { - if (innerColor & 0xFF000000) { - drawList->AddConvexPolyFilled(drawList->_Path.Data, drawList->_Path.Size, innerColor); - } - - drawList->PathStroke(color, true, 2.0f * outline_scale); - } else { - drawList->PathFillConvex(color); - } - } else { - auto triangleStart = rect_center_x + 0.32f * rect_w; - - auto rect_offset = -static_cast<int>(rect_w * 0.25f * 0.25f); - - rect.Min.x += rect_offset; - rect.Max.x += rect_offset; - rect_x += rect_offset; - rect_center_x += rect_offset * 0.5f; - rect_center.x += rect_offset * 0.5f; - - if (type == IconType::Circle) { - const auto c = rect_center; - - if (!filled) { - const auto r = 0.5f * rect_w / 2.0f - 0.5f; - - if (innerColor & 0xFF000000) - drawList->AddCircleFilled(c, r, innerColor, 12 + extra_segments); - drawList->AddCircle(c, r, color, 12 + extra_segments, 2.0f * outline_scale); - } else { - drawList->AddCircleFilled(c, 0.5f * rect_w / 2.0f, color, 12 + extra_segments); - } - } - - if (type == IconType::Square) { - if (filled) { - const auto r = 0.5f * rect_w / 2.0f; - const auto p0 = rect_center - ImVec2(r, r); - const auto p1 = rect_center + ImVec2(r, r); - - drawList->AddRectFilled(p0, p1, color, 0, 15 + extra_segments); - } else { - const auto r = 0.5f * rect_w / 2.0f - 0.5f; - const auto p0 = rect_center - ImVec2(r, r); - const auto p1 = rect_center + ImVec2(r, r); - - if (innerColor & 0xFF000000) - drawList->AddRectFilled(p0, p1, innerColor, 0, 15 + extra_segments); - - drawList->AddRect(p0, p1, color, 0, 15 + extra_segments, 2.0f * outline_scale); - } - } - - if (type == IconType::Grid) { - const auto r = 0.5f * rect_w / 2.0f; - const auto w = ceilf(r / 3.0f); - - const auto baseTl = ImVec2(floorf(rect_center_x - w * 2.5f), floorf(rect_center_y - w * 2.5f)); - const auto baseBr = ImVec2(floorf(baseTl.x + w), floorf(baseTl.y + w)); - - auto tl = baseTl; - auto br = baseBr; - for (int i = 0; i < 3; ++i) { - tl.x = baseTl.x; - br.x = baseBr.x; - drawList->AddRectFilled(tl, br, color); - tl.x += w * 2; - br.x += w * 2; - if (i != 1 || filled) - drawList->AddRectFilled(tl, br, color); - tl.x += w * 2; - br.x += w * 2; - drawList->AddRectFilled(tl, br, color); - - tl.y += w * 2; - br.y += w * 2; - } - - triangleStart = br.x + w + 1.0f / 24.0f * rect_w; - } - - if (type == IconType::RoundSquare) { - if (filled) { - const auto r = 0.5f * rect_w / 2.0f; - const auto cr = r * 0.5f; - const auto p0 = rect_center - ImVec2(r, r); - const auto p1 = rect_center + ImVec2(r, r); - - drawList->AddRectFilled(p0, p1, color, cr, 15); - } else { - const auto r = 0.5f * rect_w / 2.0f - 0.5f; - const auto cr = r * 0.5f; - const auto p0 = rect_center - ImVec2(r, r); - const auto p1 = rect_center + ImVec2(r, r); - - if (innerColor & 0xFF000000) - drawList->AddRectFilled(p0, p1, innerColor, cr, 15); - - drawList->AddRect(p0, p1, color, cr, 15, 2.0f * outline_scale); - } - } else if (type == IconType::Diamond) { - if (filled) { - const auto r = 0.607f * rect_w / 2.0f; - const auto c = rect_center; - - drawList->PathLineTo(c + ImVec2(0, -r)); - drawList->PathLineTo(c + ImVec2(r, 0)); - drawList->PathLineTo(c + ImVec2(0, r)); - drawList->PathLineTo(c + ImVec2(-r, 0)); - drawList->PathFillConvex(color); - } else { - const auto r = 0.607f * rect_w / 2.0f - 0.5f; - const auto c = rect_center; - - drawList->PathLineTo(c + ImVec2(0, -r)); - drawList->PathLineTo(c + ImVec2(r, 0)); - drawList->PathLineTo(c + ImVec2(0, r)); - drawList->PathLineTo(c + ImVec2(-r, 0)); - - if (innerColor & 0xFF000000) - drawList->AddConvexPolyFilled(drawList->_Path.Data, drawList->_Path.Size, innerColor); - - drawList->PathStroke(color, true, 2.0f * outline_scale); - } - } else { - const auto triangleTip = triangleStart + rect_w * (0.45f - 0.32f); - - drawList->AddTriangleFilled( - ImVec2(ceilf(triangleTip), rect_y + rect_h * 0.5f), - ImVec2(triangleStart, rect_center_y + 0.15f * rect_h), - ImVec2(triangleStart, rect_center_y - 0.15f * rect_h), - color); - } - } -} - -void ImGui::Icon(const ImVec2& size, IconType type, bool filled, const ImVec4& color, const ImVec4& innerColor) -{ - // Taken from https://github.com/thedmd/imgui-node-editor/blob/master/examples/blueprints-example/utilities/widgets.cpp - // ax::NodeEditor::Icon - - if (ImGui::IsRectVisible(size)) - { - auto cursorPos = ImGui::GetCursorScreenPos(); - auto drawList = ImGui::GetWindowDrawList(); - DrawIcon(drawList, cursorPos, cursorPos + size, type, filled, ImColor(color), ImColor(innerColor)); - } - - ImGui::Dummy(size); -} - -bool ImGui::Splitter(bool splitVertically, float thickness, float* size1, float* size2, float minSize1, float minSize2, float splitterLongAxisSize) -{ - // Taken from https://github.com/thedmd/imgui-node-editor/blob/master/examples/blueprints-example/blueprints-example.cpp - // ::Splitter - - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - ImGuiID id = window->GetID("##Splitter"); - ImRect bb; - bb.Min = window->DC.CursorPos + (splitVertically ? ImVec2(*size1, 0.0f) : ImVec2(0.0f, *size1)); - bb.Max = bb.Min + CalcItemSize(splitVertically ? ImVec2(thickness, splitterLongAxisSize) : ImVec2(splitterLongAxisSize, thickness), 0.0f, 0.0f); - return SplitterBehavior(bb, id, splitVertically ? ImGuiAxis_X : ImGuiAxis_Y, size1, size2, minSize1, minSize2, 0.0f); -} diff --git a/core/src/UI/UI_Workflows.cpp b/core/src/UI/UI_Workflows.cpp deleted file mode 100644 index c85850a..0000000 --- a/core/src/UI/UI_Workflows.cpp +++ /dev/null @@ -1,293 +0,0 @@ -#include "UI.hpp" - -#include "Model/GlobalStates.hpp" -#include "Model/Project.hpp" -#include "Model/Workflow/Nodes/DocumentNodes.hpp" -#include "Model/Workflow/Nodes/NumericNodes.hpp" -#include "Model/Workflow/Nodes/TextNodes.hpp" -#include "Model/Workflow/Nodes/UserInputNodes.hpp" -#include "Model/Workflow/Workflow.hpp" -#include "Utils/I18n.hpp" -#include "Utils/Macros.hpp" - -#include <IconsFontAwesome.h> -#include <imgui.h> -#include <imgui_node_editor.h> -#include <imgui_stdlib.h> -#include <memory> -#include <span> -#include <vector> - -namespace ImNodes = ax::NodeEditor; - -namespace CPLT_UNITY_ID { -class WorkflowUI -{ -private: - std::unique_ptr<Workflow> mWorkflow; - - ImNodes::EditorContext* mContext; - - ImNodes::NodeId mContextMenuNodeId = 0; - ImNodes::PinId mContextMenuPinId = 0; - ImNodes::LinkId mContextMenuLinkId = 0; - -public: - WorkflowUI(std::unique_ptr<Workflow> workflow) - : mWorkflow{ std::move(workflow) } - { - mContext = ImNodes::CreateEditor(); - } - - ~WorkflowUI() - { - ImNodes::DestroyEditor(mContext); - } - - void Display() - { - ImNodes::SetCurrentEditor(mContext); - ImNodes::Begin(""); - - // Defer creation of tooltip because within the node editor, cursor positioning is going to be off - const char* tooltipMessage = nullptr; - - for (auto& node : mWorkflow->GetNodes()) { - if (!node) continue; - - ImNodes::BeginNode(node->GetId()); - node->Draw(); - ImNodes::EndNode(); - } - - for (auto& conn : mWorkflow->GetConnections()) { - if (!conn.IsValid()) continue; - - auto srcId = mWorkflow->GetNodes()[conn.SourceNode]->GetOutputPinUniqueId(conn.SourcePin); - auto dstId = mWorkflow->GetNodes()[conn.DestinationNode]->GetInputPinUniqueId(conn.DestinationPin); - ImNodes::Link(conn.GetLinkId(), srcId, dstId); - } - - if (ImNodes::BeginCreate()) { - ImNodes::PinId src = 0, dst = 0; - if (ImNodes::QueryNewLink(&src, &dst)) { - if (!src || !dst) { - goto createError; - } - - auto [srcNode, srcPinId, srcIsOutput] = mWorkflow->DisassembleGlobalPinId(src); - auto [dstNode, dstPinId, dstIsOutput] = mWorkflow->DisassembleGlobalPinId(dst); - - if (srcNode == dstNode) { - ImNodes::RejectNewItem(); - goto createError; - } - - if (srcIsOutput == dstIsOutput) { - ImNodes::RejectNewItem(); - goto createError; - } - - auto srcPin = srcNode->GetOutputPin(srcPinId); - auto dstPin = dstNode->GetOutputPin(dstPinId); - - if (srcPin.MatchingType != dstPin.MatchingType) { - ImNodes::RejectNewItem(); - goto createError; - } - - if (ImNodes::AcceptNewItem()) { - mWorkflow->Connect(*srcNode, srcPinId, *dstNode, dstPinId); - } - } - - ImNodes::PinId newNodePin = 0; - if (ImNodes::QueryNewNode(&newNodePin)) { - auto [node, pinId, isOutput] = mWorkflow->DisassembleGlobalPinId(newNodePin); - - if ((isOutput && node->GetOutputPin(pinId).IsConnected()) || - (!isOutput && node->GetInputPin(pinId).IsConnected())) - { - ImNodes::RejectNewItem(); - goto createError; - } - - if (ImNodes::AcceptNewItem()) { - ImNodes::Suspend(); - ImGui::BeginPopup("CreateNodeCtxMenu"); - ImNodes::Resume(); - } - } - } - createError: - ImNodes::EndCreate(); - - if (ImNodes::BeginDelete()) { - ImNodes::LinkId deletedLinkId; - if (ImNodes::QueryDeletedLink(&deletedLinkId)) { - auto& conn = *mWorkflow->GetConnectionByLinkId(deletedLinkId); - mWorkflow->RemoveConnection(conn.Id); - } - - ImNodes::NodeId deletedNodeId; - if (ImNodes::QueryDeletedNode(&deletedNodeId)) { - auto node = mWorkflow->GetNodeByNodeId(deletedNodeId); - if (!node) { - ImNodes::RejectDeletedItem(); - goto deleteError; - } - - if (node->IsLocked()) { - ImNodes::RejectDeletedItem(); - goto deleteError; - } - } - } - deleteError: - ImNodes::EndDelete(); - - // Popups - ImNodes::Suspend(); - if (ImNodes::ShowNodeContextMenu(&mContextMenuNodeId)) { - ImGui::OpenPopup("NodeCtxMenu"); - } else if (ImNodes::ShowPinContextMenu(&mContextMenuPinId)) { - ImGui::OpenPopup("PinCtxMenu"); - } else if (ImNodes::ShowLinkContextMenu(&mContextMenuLinkId)) { - ImGui::OpenPopup("LinkCtxMenu"); - } - - if (ImGui::BeginPopup("NodeCtxMenu")) { - auto& node = *mWorkflow->GetNodeByNodeId(mContextMenuNodeId); - node.DrawDebugInfo(); - - if (ImGui::MenuItem(ICON_FA_TRASH " " I18N_TEXT("Delete", L10N_DELETE))) { - ImNodes::DeleteNode(mContextMenuNodeId); - } - - ImGui::EndPopup(); - } - - if (ImGui::BeginPopup("PinCtxMenu")) { - auto [node, pinId, isOutput] = mWorkflow->DisassembleGlobalPinId(mContextMenuPinId); - if (isOutput) { - node->DrawOutputPinDebugInfo(pinId); - } else { - node->DrawInputPinDebugInfo(pinId); - } - - if (ImGui::MenuItem(ICON_FA_UNLINK " " I18N_TEXT("Disconnect", L10N_DISCONNECT))) { - if (isOutput) { - auto& pin = node->GetOutputPin(pinId); - if (pin.IsConnected()) { - auto linkId = mWorkflow->GetConnectionById(pin.Connection)->GetLinkId(); - ImNodes::DeleteLink(linkId); - } - } else { - auto& pin = node->GetInputPin(pinId); - if (pin.IsConstantConnection()) { - // TODO - } else if (pin.IsConnected()) { - auto linkId = mWorkflow->GetConnectionById(pin.Connection)->GetLinkId(); - ImNodes::DeleteLink(linkId); - } - } - } - - ImGui::EndPopup(); - } - - if (ImGui::BeginPopup("LinkCtxMenu")) { - auto& conn = *mWorkflow->GetConnectionByLinkId(mContextMenuLinkId); - conn.DrawDebugInfo(); - - if (ImGui::MenuItem(ICON_FA_TRASH " " I18N_TEXT("Delete", L10N_DELETE))) { - ImNodes::DeleteLink(mContextMenuLinkId); - } - - ImGui::EndPopup(); - } - - if (ImGui::BeginPopup("CreateNodeCtxMenu")) { - for (int i = WorkflowNode::CG_Numeric; i < WorkflowNode::InvalidCategory; ++i) { - auto category = (WorkflowNode::Category)i; - auto members = WorkflowNode::QueryCategoryMembers(category); - - if (ImGui::BeginMenu(WorkflowNode::FormatCategory(category))) { - for (auto member : members) { - if (ImGui::MenuItem(WorkflowNode::FormatKind(member))) { - // Create node - auto uptr = WorkflowNode::CreateByKind(member); - mWorkflow->AddNode(std::move(uptr)); - } - } - ImGui::EndMenu(); - } - } - ImGui::EndPopup(); - } - - if (tooltipMessage) { - ImGui::BeginTooltip(); - ImGui::TextUnformatted(tooltipMessage); - ImGui::EndTooltip(); - } - ImNodes::Resume(); - - ImNodes::End(); - } - - void Close() - { - // TODO - } -}; -} // namespace CPLT_UNITY_ID - -void UI::WorkflowsTab() -{ - auto& project = *GlobalStates::GetInstance().GetCurrentProject(); - - static std::unique_ptr<CPLT_UNITY_ID::WorkflowUI> openWorkflow; - static AssetList::ListState state; - bool openedDummy = true; - - // Toolbar item: close - if (ImGui::Button(ICON_FA_TIMES " " I18N_TEXT("Close", L10N_CLOSE), openWorkflow == nullptr)) { - openWorkflow->Close(); - openWorkflow = nullptr; - } - - // Toolbar item: open... - ImGui::SameLine(); - if (ImGui::Button((I18N_TEXT("Open asset...", L10N_ASSET_OPEN)))) { - ImGui::OpenPopup(I18N_TEXT("Open asset", L10N_ASSET_OPEN_DIALOG_TITLE)); - } - if (ImGui::BeginPopupModal(I18N_TEXT("Open asset", L10N_ASSET_OPEN_DIALOG_TITLE), &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { - if (ImGui::Button(ICON_FA_FOLDER_OPEN " " I18N_TEXT("Open", L10N_OPEN), state.SelectedAsset == nullptr)) { - ImGui::CloseCurrentPopup(); - - auto workflow = project.Workflows.Load(*state.SelectedAsset); - openWorkflow = std::make_unique<CPLT_UNITY_ID::WorkflowUI>(std::move(workflow)); - } - ImGui::SameLine(); - project.Workflows.DisplayControls(state); - project.Workflows.DisplayDetailsList(state); - - ImGui::EndPopup(); - } - - // Toolbar item: manage... - ImGui::SameLine(); - if (ImGui::Button(I18N_TEXT("Manage assets...", L10N_ASSET_MANAGE))) { - ImGui::OpenPopup(I18N_TEXT("Manage assets", L10N_ASSET_MANAGE_DIALOG_TITLE)); - } - if (ImGui::BeginPopupModal(I18N_TEXT("Manage assets", L10N_ASSET_MANAGE_DIALOG_TITLE), &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { - project.Workflows.DisplayControls(state); - project.Workflows.DisplayDetailsList(state); - ImGui::EndPopup(); - } - - if (openWorkflow) { - openWorkflow->Display(); - } -} diff --git a/core/src/UI/fwd.hpp b/core/src/UI/fwd.hpp deleted file mode 100644 index 756e567..0000000 --- a/core/src/UI/fwd.hpp +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -// UI.hpp -namespace ImGui { -enum class IconType; -} |