aboutsummaryrefslogtreecommitdiff
path: root/core/src/UI
diff options
context:
space:
mode:
authorrtk0c <[email protected]>2022-06-30 21:38:53 -0700
committerrtk0c <[email protected]>2022-06-30 21:38:53 -0700
commit7fe47a9d5b1727a61dc724523b530762f6d6ba19 (patch)
treee95be6e66db504ed06d00b72c579565bab873277 /core/src/UI
parent2cf952088d375ac8b2f45b144462af0953436cff (diff)
Restructure project
Diffstat (limited to 'core/src/UI')
-rw-r--r--core/src/UI/UI.hpp48
-rw-r--r--core/src/UI/UI_DatabaseView.cpp668
-rw-r--r--core/src/UI/UI_Items.cpp252
-rw-r--r--core/src/UI/UI_MainWindow.cpp237
-rw-r--r--core/src/UI/UI_Settings.cpp8
-rw-r--r--core/src/UI/UI_Templates.cpp977
-rw-r--r--core/src/UI/UI_Utils.cpp315
-rw-r--r--core/src/UI/UI_Workflows.cpp293
-rw-r--r--core/src/UI/fwd.hpp6
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 = &paramPalette;
- 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;
-}