From 7fe47a9d5b1727a61dc724523b530762f6d6ba19 Mon Sep 17 00:00:00 2001 From: rtk0c Date: Thu, 30 Jun 2022 21:38:53 -0700 Subject: Restructure project --- 3rdparty/imgui/CMakeLists.txt | 2 +- CMakeLists.txt | 2 +- app/CMakeLists.txt | 184 ++++ app/fonts/FontAwesome5-Solid.otf | Bin 0 -> 591768 bytes app/fonts/NotoSansSC-LICENSE.txt | 91 ++ app/fonts/NotoSansSC-Regular.otf | Bin 0 -> 8481960 bytes app/source/Cplt/Entrypoint/Backend.hpp | 23 + app/source/Cplt/Entrypoint/Backend_DirectX11.cpp | 250 ++++++ app/source/Cplt/Entrypoint/Backend_DirectX12.cpp | 470 ++++++++++ app/source/Cplt/Entrypoint/Backend_Metal.mm | 40 + app/source/Cplt/Entrypoint/Backend_OpenGL2.cpp | 106 +++ app/source/Cplt/Entrypoint/Backend_OpenGL3.cpp | 121 +++ app/source/Cplt/Entrypoint/Backend_Vulkan.cpp | 438 +++++++++ app/source/Cplt/Entrypoint/main.cpp | 163 ++++ app/source/Cplt/Locale/zh_CN.h | 159 ++++ app/source/Cplt/Model/Assets.cpp | 306 +++++++ app/source/Cplt/Model/Assets.hpp | 130 +++ app/source/Cplt/Model/Database.cpp | 163 ++++ app/source/Cplt/Model/Database.hpp | 79 ++ app/source/Cplt/Model/Filter.cpp | 1 + app/source/Cplt/Model/Filter.hpp | 6 + app/source/Cplt/Model/GlobalStates.cpp | 163 ++++ app/source/Cplt/Model/GlobalStates.hpp | 55 ++ app/source/Cplt/Model/Items.cpp | 114 +++ app/source/Cplt/Model/Items.hpp | 253 ++++++ app/source/Cplt/Model/Project.cpp | 168 ++++ app/source/Cplt/Model/Project.hpp | 57 ++ app/source/Cplt/Model/Template/TableTemplate.cpp | 591 +++++++++++++ app/source/Cplt/Model/Template/TableTemplate.hpp | 223 +++++ .../Cplt/Model/Template/TableTemplateIterator.cpp | 52 ++ .../Cplt/Model/Template/TableTemplateIterator.hpp | 35 + app/source/Cplt/Model/Template/Template.hpp | 68 ++ app/source/Cplt/Model/Template/Template_Main.cpp | 214 +++++ app/source/Cplt/Model/Template/Template_RTTI.cpp | 29 + app/source/Cplt/Model/Template/fwd.hpp | 11 + app/source/Cplt/Model/Workflow/Evaluation.cpp | 174 ++++ app/source/Cplt/Model/Workflow/Evaluation.hpp | 67 ++ .../Cplt/Model/Workflow/Nodes/DocumentNodes.cpp | 18 + .../Cplt/Model/Workflow/Nodes/DocumentNodes.hpp | 13 + .../Cplt/Model/Workflow/Nodes/NumericNodes.cpp | 94 ++ .../Cplt/Model/Workflow/Nodes/NumericNodes.hpp | 44 + app/source/Cplt/Model/Workflow/Nodes/TextNodes.cpp | 231 +++++ app/source/Cplt/Model/Workflow/Nodes/TextNodes.hpp | 53 ++ .../Cplt/Model/Workflow/Nodes/UserInputNodes.cpp | 32 + .../Cplt/Model/Workflow/Nodes/UserInputNodes.hpp | 23 + app/source/Cplt/Model/Workflow/Nodes/fwd.hpp | 15 + app/source/Cplt/Model/Workflow/Value.hpp | 94 ++ app/source/Cplt/Model/Workflow/ValueInternals.hpp | 21 + app/source/Cplt/Model/Workflow/Value_Main.cpp | 35 + app/source/Cplt/Model/Workflow/Value_RTTI.cpp | 174 ++++ app/source/Cplt/Model/Workflow/Values/Basic.cpp | 111 +++ app/source/Cplt/Model/Workflow/Values/Basic.hpp | 67 ++ app/source/Cplt/Model/Workflow/Values/Database.cpp | 88 ++ app/source/Cplt/Model/Workflow/Values/Database.hpp | 51 ++ .../Cplt/Model/Workflow/Values/Dictionary.cpp | 49 ++ .../Cplt/Model/Workflow/Values/Dictionary.hpp | 25 + app/source/Cplt/Model/Workflow/Values/List.cpp | 100 +++ app/source/Cplt/Model/Workflow/Values/List.hpp | 50 ++ app/source/Cplt/Model/Workflow/Values/fwd.hpp | 17 + app/source/Cplt/Model/Workflow/Workflow.hpp | 316 +++++++ app/source/Cplt/Model/Workflow/Workflow_Main.cpp | 846 ++++++++++++++++++ app/source/Cplt/Model/Workflow/Workflow_RTTI.cpp | 143 +++ app/source/Cplt/Model/Workflow/fwd.hpp | 22 + app/source/Cplt/Model/fwd.hpp | 35 + app/source/Cplt/UI/UI.hpp | 48 + app/source/Cplt/UI/UI_DatabaseView.cpp | 668 ++++++++++++++ app/source/Cplt/UI/UI_Items.cpp | 252 ++++++ app/source/Cplt/UI/UI_MainWindow.cpp | 237 +++++ app/source/Cplt/UI/UI_Settings.cpp | 8 + app/source/Cplt/UI/UI_Templates.cpp | 977 +++++++++++++++++++++ app/source/Cplt/UI/UI_Utils.cpp | 315 +++++++ app/source/Cplt/UI/UI_Workflows.cpp | 293 ++++++ app/source/Cplt/UI/fwd.hpp | 6 + app/source/Cplt/Utils/Color.hpp | 202 +++++ app/source/Cplt/Utils/Hash.hpp | 15 + app/source/Cplt/Utils/I18n.hpp | 10 + app/source/Cplt/Utils/IO/Archive.cpp | 57 ++ app/source/Cplt/Utils/IO/Archive.hpp | 18 + app/source/Cplt/Utils/IO/CstdioFile.cpp | 36 + app/source/Cplt/Utils/IO/CstdioFile.hpp | 17 + app/source/Cplt/Utils/IO/DataStream.cpp | 283 ++++++ app/source/Cplt/Utils/IO/DataStream.hpp | 210 +++++ app/source/Cplt/Utils/IO/FileStream.cpp | 7 + app/source/Cplt/Utils/IO/FileStream.hpp | 97 ++ app/source/Cplt/Utils/IO/FileStream_Cstdio.inl | 126 +++ app/source/Cplt/Utils/IO/FileStream_Custom.inl | 358 ++++++++ app/source/Cplt/Utils/IO/Helper.hpp | 43 + app/source/Cplt/Utils/IO/StringIntegration.hpp | 37 + app/source/Cplt/Utils/IO/TslArrayIntegration.hpp | 50 ++ app/source/Cplt/Utils/IO/TslRobinIntegration.hpp | 78 ++ app/source/Cplt/Utils/IO/UuidIntegration.hpp | 27 + app/source/Cplt/Utils/IO/VectorIntegration.hpp | 42 + app/source/Cplt/Utils/IO/fwd.hpp | 13 + app/source/Cplt/Utils/Macros.hpp | 13 + app/source/Cplt/Utils/Math.hpp | 11 + app/source/Cplt/Utils/RTTI.hpp | 49 ++ app/source/Cplt/Utils/ScopeGuard.hpp | 39 + app/source/Cplt/Utils/Sigslot.cpp | 233 +++++ app/source/Cplt/Utils/Sigslot.hpp | 165 ++++ app/source/Cplt/Utils/Size.hpp | 65 ++ app/source/Cplt/Utils/StandardDirectories.cpp | 78 ++ app/source/Cplt/Utils/StandardDirectories.hpp | 10 + app/source/Cplt/Utils/Time.cpp | 29 + app/source/Cplt/Utils/Time.hpp | 11 + app/source/Cplt/Utils/UUID.hpp | 5 + app/source/Cplt/Utils/Variant.hpp | 33 + app/source/Cplt/Utils/Vector.hpp | 144 +++ app/source/Cplt/Utils/VectorHash.hpp | 46 + app/source/Cplt/Utils/fwd.hpp | 17 + app/source/Cplt/fwd.hpp | 5 + core/CMakeLists.txt | 184 ---- core/fonts/FontAwesome5-Solid.otf | Bin 591768 -> 0 bytes core/fonts/NotoSansSC-LICENSE.txt | 91 -- core/fonts/NotoSansSC-Regular.otf | Bin 8481960 -> 0 bytes core/src/Entrypoint/Backend.hpp | 23 - core/src/Entrypoint/Backend_DirectX11.cpp | 250 ------ core/src/Entrypoint/Backend_DirectX12.cpp | 470 ---------- core/src/Entrypoint/Backend_Metal.mm | 40 - core/src/Entrypoint/Backend_OpenGL2.cpp | 106 --- core/src/Entrypoint/Backend_OpenGL3.cpp | 121 --- core/src/Entrypoint/Backend_Vulkan.cpp | 438 --------- core/src/Entrypoint/main.cpp | 163 ---- core/src/Locale/zh_CN.h | 159 ---- core/src/Model/Assets.cpp | 306 ------- core/src/Model/Assets.hpp | 130 --- core/src/Model/Database.cpp | 163 ---- core/src/Model/Database.hpp | 79 -- core/src/Model/Filter.cpp | 1 - core/src/Model/Filter.hpp | 6 - core/src/Model/GlobalStates.cpp | 163 ---- core/src/Model/GlobalStates.hpp | 55 -- core/src/Model/Items.cpp | 114 --- core/src/Model/Items.hpp | 253 ------ core/src/Model/Project.cpp | 168 ---- core/src/Model/Project.hpp | 57 -- core/src/Model/Template/TableTemplate.cpp | 591 ------------- core/src/Model/Template/TableTemplate.hpp | 223 ----- core/src/Model/Template/TableTemplateIterator.cpp | 52 -- core/src/Model/Template/TableTemplateIterator.hpp | 35 - core/src/Model/Template/Template.hpp | 68 -- core/src/Model/Template/Template_Main.cpp | 214 ----- core/src/Model/Template/Template_RTTI.cpp | 29 - core/src/Model/Template/fwd.hpp | 11 - core/src/Model/Workflow/Evaluation.cpp | 174 ---- core/src/Model/Workflow/Evaluation.hpp | 67 -- core/src/Model/Workflow/Nodes/DocumentNodes.cpp | 18 - core/src/Model/Workflow/Nodes/DocumentNodes.hpp | 13 - core/src/Model/Workflow/Nodes/NumericNodes.cpp | 94 -- core/src/Model/Workflow/Nodes/NumericNodes.hpp | 44 - core/src/Model/Workflow/Nodes/TextNodes.cpp | 231 ----- core/src/Model/Workflow/Nodes/TextNodes.hpp | 53 -- core/src/Model/Workflow/Nodes/UserInputNodes.cpp | 32 - core/src/Model/Workflow/Nodes/UserInputNodes.hpp | 23 - core/src/Model/Workflow/Nodes/fwd.hpp | 15 - core/src/Model/Workflow/Value.hpp | 94 -- core/src/Model/Workflow/ValueInternals.hpp | 21 - core/src/Model/Workflow/Value_Main.cpp | 35 - core/src/Model/Workflow/Value_RTTI.cpp | 174 ---- core/src/Model/Workflow/Values/Basic.cpp | 111 --- core/src/Model/Workflow/Values/Basic.hpp | 67 -- core/src/Model/Workflow/Values/Database.cpp | 88 -- core/src/Model/Workflow/Values/Database.hpp | 51 -- core/src/Model/Workflow/Values/Dictionary.cpp | 49 -- core/src/Model/Workflow/Values/Dictionary.hpp | 25 - core/src/Model/Workflow/Values/List.cpp | 100 --- core/src/Model/Workflow/Values/List.hpp | 50 -- core/src/Model/Workflow/Values/fwd.hpp | 17 - core/src/Model/Workflow/Workflow.hpp | 316 ------- core/src/Model/Workflow/Workflow_Main.cpp | 846 ------------------ core/src/Model/Workflow/Workflow_RTTI.cpp | 143 --- core/src/Model/Workflow/fwd.hpp | 22 - core/src/Model/fwd.hpp | 35 - core/src/UI/UI.hpp | 48 - core/src/UI/UI_DatabaseView.cpp | 668 -------------- core/src/UI/UI_Items.cpp | 252 ------ core/src/UI/UI_MainWindow.cpp | 237 ----- core/src/UI/UI_Settings.cpp | 8 - core/src/UI/UI_Templates.cpp | 977 --------------------- core/src/UI/UI_Utils.cpp | 315 ------- core/src/UI/UI_Workflows.cpp | 293 ------ core/src/UI/fwd.hpp | 6 - core/src/Utils/Color.hpp | 202 ----- core/src/Utils/Hash.hpp | 15 - core/src/Utils/I18n.hpp | 10 - core/src/Utils/IO/Archive.cpp | 57 -- core/src/Utils/IO/Archive.hpp | 18 - core/src/Utils/IO/CstdioFile.cpp | 36 - core/src/Utils/IO/CstdioFile.hpp | 17 - core/src/Utils/IO/DataStream.cpp | 283 ------ core/src/Utils/IO/DataStream.hpp | 210 ----- core/src/Utils/IO/FileStream.cpp | 7 - core/src/Utils/IO/FileStream.hpp | 97 -- core/src/Utils/IO/FileStream_Cstdio.inl | 126 --- core/src/Utils/IO/FileStream_Custom.inl | 358 -------- core/src/Utils/IO/Helper.hpp | 43 - core/src/Utils/IO/StringIntegration.hpp | 37 - core/src/Utils/IO/TslArrayIntegration.hpp | 50 -- core/src/Utils/IO/TslRobinIntegration.hpp | 78 -- core/src/Utils/IO/UuidIntegration.hpp | 27 - core/src/Utils/IO/VectorIntegration.hpp | 42 - core/src/Utils/IO/fwd.hpp | 13 - core/src/Utils/Macros.hpp | 13 - core/src/Utils/Math.hpp | 11 - core/src/Utils/RTTI.hpp | 49 -- core/src/Utils/ScopeGuard.hpp | 39 - core/src/Utils/Sigslot.cpp | 233 ----- core/src/Utils/Sigslot.hpp | 165 ---- core/src/Utils/Size.hpp | 65 -- core/src/Utils/StandardDirectories.cpp | 78 -- core/src/Utils/StandardDirectories.hpp | 10 - core/src/Utils/Time.cpp | 29 - core/src/Utils/Time.hpp | 11 - core/src/Utils/UUID.hpp | 5 - core/src/Utils/Variant.hpp | 33 - core/src/Utils/Vector.hpp | 144 --- core/src/Utils/VectorHash.hpp | 46 - core/src/Utils/fwd.hpp | 17 - core/src/cplt_fwd.hpp | 5 - 218 files changed, 13256 insertions(+), 13256 deletions(-) create mode 100644 app/CMakeLists.txt create mode 100644 app/fonts/FontAwesome5-Solid.otf create mode 100644 app/fonts/NotoSansSC-LICENSE.txt create mode 100644 app/fonts/NotoSansSC-Regular.otf create mode 100644 app/source/Cplt/Entrypoint/Backend.hpp create mode 100644 app/source/Cplt/Entrypoint/Backend_DirectX11.cpp create mode 100644 app/source/Cplt/Entrypoint/Backend_DirectX12.cpp create mode 100644 app/source/Cplt/Entrypoint/Backend_Metal.mm create mode 100644 app/source/Cplt/Entrypoint/Backend_OpenGL2.cpp create mode 100644 app/source/Cplt/Entrypoint/Backend_OpenGL3.cpp create mode 100644 app/source/Cplt/Entrypoint/Backend_Vulkan.cpp create mode 100644 app/source/Cplt/Entrypoint/main.cpp create mode 100644 app/source/Cplt/Locale/zh_CN.h create mode 100644 app/source/Cplt/Model/Assets.cpp create mode 100644 app/source/Cplt/Model/Assets.hpp create mode 100644 app/source/Cplt/Model/Database.cpp create mode 100644 app/source/Cplt/Model/Database.hpp create mode 100644 app/source/Cplt/Model/Filter.cpp create mode 100644 app/source/Cplt/Model/Filter.hpp create mode 100644 app/source/Cplt/Model/GlobalStates.cpp create mode 100644 app/source/Cplt/Model/GlobalStates.hpp create mode 100644 app/source/Cplt/Model/Items.cpp create mode 100644 app/source/Cplt/Model/Items.hpp create mode 100644 app/source/Cplt/Model/Project.cpp create mode 100644 app/source/Cplt/Model/Project.hpp create mode 100644 app/source/Cplt/Model/Template/TableTemplate.cpp create mode 100644 app/source/Cplt/Model/Template/TableTemplate.hpp create mode 100644 app/source/Cplt/Model/Template/TableTemplateIterator.cpp create mode 100644 app/source/Cplt/Model/Template/TableTemplateIterator.hpp create mode 100644 app/source/Cplt/Model/Template/Template.hpp create mode 100644 app/source/Cplt/Model/Template/Template_Main.cpp create mode 100644 app/source/Cplt/Model/Template/Template_RTTI.cpp create mode 100644 app/source/Cplt/Model/Template/fwd.hpp create mode 100644 app/source/Cplt/Model/Workflow/Evaluation.cpp create mode 100644 app/source/Cplt/Model/Workflow/Evaluation.hpp create mode 100644 app/source/Cplt/Model/Workflow/Nodes/DocumentNodes.cpp create mode 100644 app/source/Cplt/Model/Workflow/Nodes/DocumentNodes.hpp create mode 100644 app/source/Cplt/Model/Workflow/Nodes/NumericNodes.cpp create mode 100644 app/source/Cplt/Model/Workflow/Nodes/NumericNodes.hpp create mode 100644 app/source/Cplt/Model/Workflow/Nodes/TextNodes.cpp create mode 100644 app/source/Cplt/Model/Workflow/Nodes/TextNodes.hpp create mode 100644 app/source/Cplt/Model/Workflow/Nodes/UserInputNodes.cpp create mode 100644 app/source/Cplt/Model/Workflow/Nodes/UserInputNodes.hpp create mode 100644 app/source/Cplt/Model/Workflow/Nodes/fwd.hpp create mode 100644 app/source/Cplt/Model/Workflow/Value.hpp create mode 100644 app/source/Cplt/Model/Workflow/ValueInternals.hpp create mode 100644 app/source/Cplt/Model/Workflow/Value_Main.cpp create mode 100644 app/source/Cplt/Model/Workflow/Value_RTTI.cpp create mode 100644 app/source/Cplt/Model/Workflow/Values/Basic.cpp create mode 100644 app/source/Cplt/Model/Workflow/Values/Basic.hpp create mode 100644 app/source/Cplt/Model/Workflow/Values/Database.cpp create mode 100644 app/source/Cplt/Model/Workflow/Values/Database.hpp create mode 100644 app/source/Cplt/Model/Workflow/Values/Dictionary.cpp create mode 100644 app/source/Cplt/Model/Workflow/Values/Dictionary.hpp create mode 100644 app/source/Cplt/Model/Workflow/Values/List.cpp create mode 100644 app/source/Cplt/Model/Workflow/Values/List.hpp create mode 100644 app/source/Cplt/Model/Workflow/Values/fwd.hpp create mode 100644 app/source/Cplt/Model/Workflow/Workflow.hpp create mode 100644 app/source/Cplt/Model/Workflow/Workflow_Main.cpp create mode 100644 app/source/Cplt/Model/Workflow/Workflow_RTTI.cpp create mode 100644 app/source/Cplt/Model/Workflow/fwd.hpp create mode 100644 app/source/Cplt/Model/fwd.hpp create mode 100644 app/source/Cplt/UI/UI.hpp create mode 100644 app/source/Cplt/UI/UI_DatabaseView.cpp create mode 100644 app/source/Cplt/UI/UI_Items.cpp create mode 100644 app/source/Cplt/UI/UI_MainWindow.cpp create mode 100644 app/source/Cplt/UI/UI_Settings.cpp create mode 100644 app/source/Cplt/UI/UI_Templates.cpp create mode 100644 app/source/Cplt/UI/UI_Utils.cpp create mode 100644 app/source/Cplt/UI/UI_Workflows.cpp create mode 100644 app/source/Cplt/UI/fwd.hpp create mode 100644 app/source/Cplt/Utils/Color.hpp create mode 100644 app/source/Cplt/Utils/Hash.hpp create mode 100644 app/source/Cplt/Utils/I18n.hpp create mode 100644 app/source/Cplt/Utils/IO/Archive.cpp create mode 100644 app/source/Cplt/Utils/IO/Archive.hpp create mode 100644 app/source/Cplt/Utils/IO/CstdioFile.cpp create mode 100644 app/source/Cplt/Utils/IO/CstdioFile.hpp create mode 100644 app/source/Cplt/Utils/IO/DataStream.cpp create mode 100644 app/source/Cplt/Utils/IO/DataStream.hpp create mode 100644 app/source/Cplt/Utils/IO/FileStream.cpp create mode 100644 app/source/Cplt/Utils/IO/FileStream.hpp create mode 100644 app/source/Cplt/Utils/IO/FileStream_Cstdio.inl create mode 100644 app/source/Cplt/Utils/IO/FileStream_Custom.inl create mode 100644 app/source/Cplt/Utils/IO/Helper.hpp create mode 100644 app/source/Cplt/Utils/IO/StringIntegration.hpp create mode 100644 app/source/Cplt/Utils/IO/TslArrayIntegration.hpp create mode 100644 app/source/Cplt/Utils/IO/TslRobinIntegration.hpp create mode 100644 app/source/Cplt/Utils/IO/UuidIntegration.hpp create mode 100644 app/source/Cplt/Utils/IO/VectorIntegration.hpp create mode 100644 app/source/Cplt/Utils/IO/fwd.hpp create mode 100644 app/source/Cplt/Utils/Macros.hpp create mode 100644 app/source/Cplt/Utils/Math.hpp create mode 100644 app/source/Cplt/Utils/RTTI.hpp create mode 100644 app/source/Cplt/Utils/ScopeGuard.hpp create mode 100644 app/source/Cplt/Utils/Sigslot.cpp create mode 100644 app/source/Cplt/Utils/Sigslot.hpp create mode 100644 app/source/Cplt/Utils/Size.hpp create mode 100644 app/source/Cplt/Utils/StandardDirectories.cpp create mode 100644 app/source/Cplt/Utils/StandardDirectories.hpp create mode 100644 app/source/Cplt/Utils/Time.cpp create mode 100644 app/source/Cplt/Utils/Time.hpp create mode 100644 app/source/Cplt/Utils/UUID.hpp create mode 100644 app/source/Cplt/Utils/Variant.hpp create mode 100644 app/source/Cplt/Utils/Vector.hpp create mode 100644 app/source/Cplt/Utils/VectorHash.hpp create mode 100644 app/source/Cplt/Utils/fwd.hpp create mode 100644 app/source/Cplt/fwd.hpp delete mode 100644 core/CMakeLists.txt delete mode 100644 core/fonts/FontAwesome5-Solid.otf delete mode 100644 core/fonts/NotoSansSC-LICENSE.txt delete mode 100644 core/fonts/NotoSansSC-Regular.otf delete mode 100644 core/src/Entrypoint/Backend.hpp delete mode 100644 core/src/Entrypoint/Backend_DirectX11.cpp delete mode 100644 core/src/Entrypoint/Backend_DirectX12.cpp delete mode 100644 core/src/Entrypoint/Backend_Metal.mm delete mode 100644 core/src/Entrypoint/Backend_OpenGL2.cpp delete mode 100644 core/src/Entrypoint/Backend_OpenGL3.cpp delete mode 100644 core/src/Entrypoint/Backend_Vulkan.cpp delete mode 100644 core/src/Entrypoint/main.cpp delete mode 100644 core/src/Locale/zh_CN.h delete mode 100644 core/src/Model/Assets.cpp delete mode 100644 core/src/Model/Assets.hpp delete mode 100644 core/src/Model/Database.cpp delete mode 100644 core/src/Model/Database.hpp delete mode 100644 core/src/Model/Filter.cpp delete mode 100644 core/src/Model/Filter.hpp delete mode 100644 core/src/Model/GlobalStates.cpp delete mode 100644 core/src/Model/GlobalStates.hpp delete mode 100644 core/src/Model/Items.cpp delete mode 100644 core/src/Model/Items.hpp delete mode 100644 core/src/Model/Project.cpp delete mode 100644 core/src/Model/Project.hpp delete mode 100644 core/src/Model/Template/TableTemplate.cpp delete mode 100644 core/src/Model/Template/TableTemplate.hpp delete mode 100644 core/src/Model/Template/TableTemplateIterator.cpp delete mode 100644 core/src/Model/Template/TableTemplateIterator.hpp delete mode 100644 core/src/Model/Template/Template.hpp delete mode 100644 core/src/Model/Template/Template_Main.cpp delete mode 100644 core/src/Model/Template/Template_RTTI.cpp delete mode 100644 core/src/Model/Template/fwd.hpp delete mode 100644 core/src/Model/Workflow/Evaluation.cpp delete mode 100644 core/src/Model/Workflow/Evaluation.hpp delete mode 100644 core/src/Model/Workflow/Nodes/DocumentNodes.cpp delete mode 100644 core/src/Model/Workflow/Nodes/DocumentNodes.hpp delete mode 100644 core/src/Model/Workflow/Nodes/NumericNodes.cpp delete mode 100644 core/src/Model/Workflow/Nodes/NumericNodes.hpp delete mode 100644 core/src/Model/Workflow/Nodes/TextNodes.cpp delete mode 100644 core/src/Model/Workflow/Nodes/TextNodes.hpp delete mode 100644 core/src/Model/Workflow/Nodes/UserInputNodes.cpp delete mode 100644 core/src/Model/Workflow/Nodes/UserInputNodes.hpp delete mode 100644 core/src/Model/Workflow/Nodes/fwd.hpp delete mode 100644 core/src/Model/Workflow/Value.hpp delete mode 100644 core/src/Model/Workflow/ValueInternals.hpp delete mode 100644 core/src/Model/Workflow/Value_Main.cpp delete mode 100644 core/src/Model/Workflow/Value_RTTI.cpp delete mode 100644 core/src/Model/Workflow/Values/Basic.cpp delete mode 100644 core/src/Model/Workflow/Values/Basic.hpp delete mode 100644 core/src/Model/Workflow/Values/Database.cpp delete mode 100644 core/src/Model/Workflow/Values/Database.hpp delete mode 100644 core/src/Model/Workflow/Values/Dictionary.cpp delete mode 100644 core/src/Model/Workflow/Values/Dictionary.hpp delete mode 100644 core/src/Model/Workflow/Values/List.cpp delete mode 100644 core/src/Model/Workflow/Values/List.hpp delete mode 100644 core/src/Model/Workflow/Values/fwd.hpp delete mode 100644 core/src/Model/Workflow/Workflow.hpp delete mode 100644 core/src/Model/Workflow/Workflow_Main.cpp delete mode 100644 core/src/Model/Workflow/Workflow_RTTI.cpp delete mode 100644 core/src/Model/Workflow/fwd.hpp delete mode 100644 core/src/Model/fwd.hpp delete mode 100644 core/src/UI/UI.hpp delete mode 100644 core/src/UI/UI_DatabaseView.cpp delete mode 100644 core/src/UI/UI_Items.cpp delete mode 100644 core/src/UI/UI_MainWindow.cpp delete mode 100644 core/src/UI/UI_Settings.cpp delete mode 100644 core/src/UI/UI_Templates.cpp delete mode 100644 core/src/UI/UI_Utils.cpp delete mode 100644 core/src/UI/UI_Workflows.cpp delete mode 100644 core/src/UI/fwd.hpp delete mode 100644 core/src/Utils/Color.hpp delete mode 100644 core/src/Utils/Hash.hpp delete mode 100644 core/src/Utils/I18n.hpp delete mode 100644 core/src/Utils/IO/Archive.cpp delete mode 100644 core/src/Utils/IO/Archive.hpp delete mode 100644 core/src/Utils/IO/CstdioFile.cpp delete mode 100644 core/src/Utils/IO/CstdioFile.hpp delete mode 100644 core/src/Utils/IO/DataStream.cpp delete mode 100644 core/src/Utils/IO/DataStream.hpp delete mode 100644 core/src/Utils/IO/FileStream.cpp delete mode 100644 core/src/Utils/IO/FileStream.hpp delete mode 100644 core/src/Utils/IO/FileStream_Cstdio.inl delete mode 100644 core/src/Utils/IO/FileStream_Custom.inl delete mode 100644 core/src/Utils/IO/Helper.hpp delete mode 100644 core/src/Utils/IO/StringIntegration.hpp delete mode 100644 core/src/Utils/IO/TslArrayIntegration.hpp delete mode 100644 core/src/Utils/IO/TslRobinIntegration.hpp delete mode 100644 core/src/Utils/IO/UuidIntegration.hpp delete mode 100644 core/src/Utils/IO/VectorIntegration.hpp delete mode 100644 core/src/Utils/IO/fwd.hpp delete mode 100644 core/src/Utils/Macros.hpp delete mode 100644 core/src/Utils/Math.hpp delete mode 100644 core/src/Utils/RTTI.hpp delete mode 100644 core/src/Utils/ScopeGuard.hpp delete mode 100644 core/src/Utils/Sigslot.cpp delete mode 100644 core/src/Utils/Sigslot.hpp delete mode 100644 core/src/Utils/Size.hpp delete mode 100644 core/src/Utils/StandardDirectories.cpp delete mode 100644 core/src/Utils/StandardDirectories.hpp delete mode 100644 core/src/Utils/Time.cpp delete mode 100644 core/src/Utils/Time.hpp delete mode 100644 core/src/Utils/UUID.hpp delete mode 100644 core/src/Utils/Variant.hpp delete mode 100644 core/src/Utils/Vector.hpp delete mode 100644 core/src/Utils/VectorHash.hpp delete mode 100644 core/src/Utils/fwd.hpp delete mode 100644 core/src/cplt_fwd.hpp diff --git a/3rdparty/imgui/CMakeLists.txt b/3rdparty/imgui/CMakeLists.txt index 4bdfe9c..a491ca0 100644 --- a/3rdparty/imgui/CMakeLists.txt +++ b/3rdparty/imgui/CMakeLists.txt @@ -4,7 +4,7 @@ file(GLOB IMGUI_SOURCES *.cpp) # depending on build flags. Technically it is possible to write then here too, but doing so would require repeating # the build flags twice both in here and in core/CMakeLists.txt -add_library(imgui ${IMGUI_SOURCES} ../../core/src/Model/Template/TableTemplateIterator.hpp) +add_library(imgui ${IMGUI_SOURCES}) target_include_directories(imgui PUBLIC ${CMAKE_CURRENT_LIST_DIR} diff --git a/CMakeLists.txt b/CMakeLists.txt index da50547..0edd517 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,4 +20,4 @@ add_subdirectory(3rdparty/imgui) add_subdirectory(3rdparty/implot) add_subdirectory(3rdparty/imgui-node-editor) add_subdirectory(3rdparty/sqlitecpp) -add_subdirectory(core) +add_subdirectory(app) diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt new file mode 100644 index 0000000..e898800 --- /dev/null +++ b/app/CMakeLists.txt @@ -0,0 +1,184 @@ +option(BUILD_CORE_TESTS "Whether to make executable tests or not." OFF) + +set(TARGET_LOCALE "en_US" CACHE STRING "Locale to build.") + +option(BUILD_CORE_WITH_OPENGL2_BACKEND ON) +option(BUILD_CORE_WITH_OPENGL3_BACKEND ON) +option(BUILD_CORE_WITH_VULKAN_BACKEND ON) +if(WIN32) + option(BUILD_CORE_WITH_DX11_BACKEND ON) + option(BUILD_CORE_WITH_DX12_BACKEND ON) +elseif(APPLE) + option(BUILD_CORE_WITH_METAL_BACKEND ON) +endif() + +message("CpltCore: generating executable CpltCore") + +if(BUILD_CORE_WITH_OPENGL2_BACKEND OR + BUILD_CORE_WITH_OPENGL3_BACKEND OR + BUILD_CORE_WITH_VULKAN_BACKEND OR + BUILD_CORE_WITH_METAL_BACKEND) + list(APPEND CPLT_CORE_SOURCES_SPECIAL + ${CMAKE_SOURCE_DIR}/3rdparty/imgui/backend/imgui_impl_glfw.cpp +) +endif() +if(BUILD_CORE_WITH_DX11_BACKEND OR BUILD_CORE_WITH_DX12_BACKEND) + list(APPEND CPLT_CORE_SOURCES_SPECIAL + ${CMAKE_SOURCE_DIR}/3rdparty/imgui/backend/imgui_impl_win32.cpp + ) +endif() + +file(GLOB_RECURSE CPLT_CORE_SOURCES_NORMAL + ${CMAKE_CURRENT_LIST_DIR}/source/*.h + ${CMAKE_CURRENT_LIST_DIR}/source/*.c + ${CMAKE_CURRENT_LIST_DIR}/source/*.m + ${CMAKE_CURRENT_LIST_DIR}/source/*.hpp + ${CMAKE_CURRENT_LIST_DIR}/source/*.cpp + ${CMAKE_CURRENT_LIST_DIR}/source/*.mm +) + +add_executable(CpltCore ${CPLT_CORE_SOURCES_SPECIAL} ${CPLT_CORE_SOURCES_NORMAL}) +set_target_properties(CpltCore +PROPERTIES + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED ON + CXX_EXTENSIONS OFF +) + +set(CPLT_CORE_SOURCES_NORMAL_UNITY_EXCLUDED + source/Cplt/Entrypoint/main.cpp + source/Cplt/Entrypoint/Backend_OpenGL2.cpp + source/Cplt/Entrypoint/Backend_OpenGL3.cpp + source/Cplt/Entrypoint/Backend_Vulkan.cpp + source/Cplt/Entrypoint/Backend_DirectX11.cpp + source/Cplt/Entrypoint/Backend_DirectX12.cpp + source/Cplt/Entrypoint/Backend_Metal.mm + source/Cplt/UI/UI_DatabaseView.cpp + source/Cplt/UI/UI_Items.cpp + source/Cplt/UI/UI_MainWindow.cpp + source/Cplt/UI/UI_Settings.cpp + source/Cplt/UI/UI_Templates.cpp + source/Cplt/UI/UI_Utils.cpp + source/Cplt/UI/UI_Workflows.cpp + source/Cplt/Utils/IO/FileStream.cpp +) +set_source_files_properties( + ${CPLT_CORE_SOURCES_SPECIAL} + ${CPLT_CORE_SOURCES_NORMAL_UNITY_EXCLUDED} +PROPERTIES + SKIP_UNITY_BUILD_INCLUSION ON +) + +target_include_directories(CpltCore PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/source +) +target_link_libraries(CpltCore +PRIVATE + ${CONAN_LIBS} + icon-font-headers + imgui + implot + imgui-node-editor + SQLiteCpp +) + +if(NOT BUILD_CORE_TESTS) + target_compile_definitions(CpltCore PRIVATE DOCTEST_CONFIG_DISABLE=1) +endif() + +if(NOT TARGET_LOCALE STREQUAL "en_US") + target_compile_definitions(CpltCore + PRIVATE + TARGET_LOCALE=${TARGET_LOCALE} + TARGET_LOCALE_FILE="Locale/${TARGET_LOCALE}.h" + ) +endif() + +if(WIN32) + message("CpltCore: - building with DirectX11 backend ${BUILD_CORE_WITH_DX11_BACKEND}") + message("CpltCore: - building with DirectX12 backend ${BUILD_CORE_WITH_DX12_BACKEND}") + target_compile_definitions(CpltCore + PRIVATE + BUILD_CORE_WITH_DX11_BACKEND=$ + BUILD_CORE_WITH_DX12_BACKEND=$ + ) + + if(BUILD_CORE_WITH_DX11_BACKEND) + target_link_libraries(CpltCore PRIVATE dxgi.lib d3d11.lib) + endif() + if(BUILD_CORE_WITH_DX12_BACKEND) + target_link_libraries(CpltCore PRIVATE dxgi.lib d3d12.lib) + endif() +endif() + +if(APPLE) + message("CpltCore: - building with Metal backend ${BUILD_CORE_WITH_METAL_BACKEND}") + target_compile_definitions(CpltCore + PRIVATE + BUILD_CORE_WITH_METAL_BACKEND=$ + ) +endif() + +if(NOT APPLE) + message("CpltCore: - building with OpenGL2 backend ${BUILD_CORE_WITH_OPENGL2_BACKEND}") + message("CpltCore: - building with OpenGL3 backend ${BUILD_CORE_WITH_OPENGL3_BACKEND}") + message("CpltCore: - building with Vulkan backend ${BUILD_CORE_WITH_VULKAN_BACKEND}") + target_compile_definitions(CpltCore + PRIVATE + BUILD_CORE_WITH_OPENGL2_BACKEND=$ + BUILD_CORE_WITH_OPENGL3_BACKEND=$ + BUILD_CORE_WITH_VULKAN_BACKEND=$ + ) + + # TODO conditionally add opengl libraries + if(BUILD_CORE_WITH_VULKAN_BACKEND) + find_package(Vulkan REQUIRED FATAL_ERROR) + target_include_directories(CpltCore PRIVATE ${Vulkan_INCLUDE_DIRS}) + target_link_libraries(CpltCore PRIVATE ${Vulkan_LIBRARIES}) + endif() +endif() + +if(WIN32) + function(handle_gnu_style_compiler) + # No console window when targeting windows + # Supposedly the flag -mwindows would automatically make the executable use GUI subsystem + # But, when subsystem is set to GUI, linker will only search WinMain and wWinMain but not the standard main (it seems like) + # so creating GUI executable fails and the linker silently reverts to the default, CUI subsystem + target_link_options(CpltCore PRIVATE -Wl,-subsystem:windows) + target_link_options(CpltCore PRIVATE -Wl,-entry:mainCRTStartup) + endfunction() + + function(handle_msvc_style_compiler) + # No console window when targeting windows + target_link_options(CpltCore PRIVATE /SUBSYSTEM:windows /ENTRY:mainCRTStartup) + endfunction() + + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + # GCC (MinGW) + handle_gnu_style_compiler() + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + # MSVC-style argument clang (clang-cl.exe) + handle_msvc_style_compiler() + elseif(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "GNU") + # GNU-style argument clang (clang.exe and clang++.exe) + handle_gnu_style_compiler() + endif() + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + handle_msvc_style_compiler() + + # Use updated __cplusplus macro + # https://docs.microsoft.com/en-us/cpp/build/reference/zc-cplusplus + target_compile_options(CpltCore PUBLIC /Zc:__cplusplus) + endif() +endif() + +if(CMAKE_UNITY_BUILD) + message("CpltCore: - using unity build") + set_target_properties(CpltCore PROPERTIES + UNITY_BUILD_MODE BATCH + UNITY_BUILD_UNIQUE_ID "CPLT_UNITY_ID" + ) +else() + message("CpltCore: - using regular build") +endif() diff --git a/app/fonts/FontAwesome5-Solid.otf b/app/fonts/FontAwesome5-Solid.otf new file mode 100644 index 0000000..8fb28d5 Binary files /dev/null and b/app/fonts/FontAwesome5-Solid.otf differ diff --git a/app/fonts/NotoSansSC-LICENSE.txt b/app/fonts/NotoSansSC-LICENSE.txt new file mode 100644 index 0000000..77b1731 --- /dev/null +++ b/app/fonts/NotoSansSC-LICENSE.txt @@ -0,0 +1,91 @@ +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/app/fonts/NotoSansSC-Regular.otf b/app/fonts/NotoSansSC-Regular.otf new file mode 100644 index 0000000..fdb0506 Binary files /dev/null and b/app/fonts/NotoSansSC-Regular.otf differ diff --git a/app/source/Cplt/Entrypoint/Backend.hpp b/app/source/Cplt/Entrypoint/Backend.hpp new file mode 100644 index 0000000..ca391e6 --- /dev/null +++ b/app/source/Cplt/Entrypoint/Backend.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include + +class RenderingBackend +{ +public: + // Implemented in Backend_OpenGL2.cpp + static std::unique_ptr CreateOpenGL2Backend(); + // Implemented in Backend_OpenGL3.cpp + static std::unique_ptr CreateOpenGL3Backend(); + // Implemented in Backend_Vulkan.cpp + static std::unique_ptr CreateVulkanBackend(); + // Implemented in Backend_DirectX11.cpp + static std::unique_ptr CreateDx11Backend(); + // Implemented in Backend_DirectX12.cpp + static std::unique_ptr CreateDx12Backend(); + // Implemented in Backend_Metal.cpp + static std::unique_ptr CreateMetalBackend(); + + virtual ~RenderingBackend() = default; + virtual void RunUntilWindowClose(void (*windowContent)()) = 0; +}; diff --git a/app/source/Cplt/Entrypoint/Backend_DirectX11.cpp b/app/source/Cplt/Entrypoint/Backend_DirectX11.cpp new file mode 100644 index 0000000..4dc33f7 --- /dev/null +++ b/app/source/Cplt/Entrypoint/Backend_DirectX11.cpp @@ -0,0 +1,250 @@ +#include "Backend.hpp" + +#if BUILD_CORE_WITH_DX11_BACKEND +# include +# include +# include +# include +# include +# include + +// Forward declare message handler from imgui_impl_win32.cpp +extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); + +class DirectX11Backend : public RenderingBackend +{ +private: + HWND hWnd; + WNDCLASSEX wc; + + ID3D11Device* mD3dDevice = nullptr; + ID3D11DeviceContext* mD3dDeviceContext = nullptr; + IDXGISwapChain* mSwapChain = nullptr; + ID3D11RenderTargetView* mMainRenderTargetView = nullptr; + +public: + DirectX11Backend() + { + ImGui_ImplWin32_EnableDpiAwareness(); + + wc.cbSize = sizeof(WNDCLASSEX); + wc.style = CS_CLASSDC; + wc.lpfnWndProc = &StaticWndProc; + wc.cbClsExtra = 0L; + wc.cbWndExtra = 0L; + wc.hInstance = GetModuleHandle(nullptr); + wc.hIcon = nullptr; + wc.hCursor = nullptr; + wc.hbrBackground = nullptr; + wc.lpszMenuName = nullptr; + wc.lpszClassName = _T("Cplt"); + wc.hIconSm = nullptr; + ::RegisterClassEx(&wc); + + hWnd = ::CreateWindow( + wc.lpszClassName, + _T("Cplt main window"), + WS_OVERLAPPEDWINDOW, + /* x */ 100, + /* y */ 100, + /* window width */ 1280, + /* window height */ 800, + nullptr, + nullptr, + wc.hInstance, + this); + + if (!CreateDeviceD3D()) { + CleanupDeviceD3D(); + ::UnregisterClass(wc.lpszClassName, wc.hInstance); + throw std::runtime_error("Failed to create d3d device."); + } + + ::ShowWindow(hWnd, SW_SHOWDEFAULT); + ::UpdateWindow(hWnd); + + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + + ImGui_ImplWin32_Init(hWnd); + ImGui_ImplDX11_Init(mD3dDevice, mD3dDeviceContext); + } + + virtual ~DirectX11Backend() + { + ImGui_ImplDX11_Shutdown(); + ImGui_ImplWin32_Shutdown(); + ImGui::DestroyContext(); + + CleanupDeviceD3D(); + ::DestroyWindow(hWnd); + ::UnregisterClass(wc.lpszClassName, wc.hInstance); + } + + virtual void RunUntilWindowClose(void (*windowContent)()) + { + while (true) { + MSG msg; + bool done = false; + while (::PeekMessage(&msg, nullptr, 0U, 0U, PM_REMOVE)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + if (msg.message == WM_QUIT) { + done = true; + } + } + if (done) break; + + ImGui_ImplDX11_NewFrame(); + ImGui_ImplWin32_NewFrame(); + ImGui::NewFrame(); + + windowContent(); + + ImGui::Render(); + const ImVec4 kClearColor = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + const float kClearColorWithAlpha[4] = { kClearColor.x * kClearColor.w, kClearColor.y * kClearColor.w, kClearColor.z * kClearColor.w, kClearColor.w }; + mD3dDeviceContext->OMSetRenderTargets(1, &mMainRenderTargetView, nullptr); + mD3dDeviceContext->ClearRenderTargetView(mMainRenderTargetView, kClearColorWithAlpha); + ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData()); + + mSwapChain->Present(1, 0); // Present with vsync + } + } + +private: + bool CreateDeviceD3D() + { + // Setup swap chain + DXGI_SWAP_CHAIN_DESC sd; + ZeroMemory(&sd, sizeof(sd)); + sd.BufferCount = 2; + sd.BufferDesc.Width = 0; + sd.BufferDesc.Height = 0; + sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + sd.BufferDesc.RefreshRate.Numerator = 60; + sd.BufferDesc.RefreshRate.Denominator = 1; + sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; + sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + sd.OutputWindow = hWnd; + sd.SampleDesc.Count = 1; + sd.SampleDesc.Quality = 0; + sd.Windowed = TRUE; + sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; + + UINT createDeviceFlags = 0; + //createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG; + D3D_FEATURE_LEVEL featureLevel; + const D3D_FEATURE_LEVEL featureLevelArray[2] = { + D3D_FEATURE_LEVEL_11_0, + D3D_FEATURE_LEVEL_10_0, + }; + if (D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, createDeviceFlags, featureLevelArray, 2, D3D11_SDK_VERSION, &sd, &mSwapChain, &mD3dDevice, &featureLevel, &mD3dDeviceContext) != S_OK) { + return false; + } + + CreateRenderTarget(); + return true; + } + + void CleanupDeviceD3D() + { + CleanupRenderTarget(); + if (mSwapChain) { + mSwapChain->Release(); + mSwapChain = nullptr; + } + if (mD3dDeviceContext) { + mD3dDeviceContext->Release(); + mD3dDeviceContext = nullptr; + } + if (mD3dDevice) { + mD3dDevice->Release(); + mD3dDevice = nullptr; + } + } + + void CreateRenderTarget() + { + ID3D11Texture2D* pBackBuffer; + mSwapChain->GetBuffer(0, IID_PPV_ARGS(&pBackBuffer)); + mD3dDevice->CreateRenderTargetView(pBackBuffer, nullptr, &mMainRenderTargetView); + pBackBuffer->Release(); + } + + void CleanupRenderTarget() + { + if (mMainRenderTargetView) { + mMainRenderTargetView->Release(); + mMainRenderTargetView = nullptr; + } + } + + static LRESULT CALLBACK StaticWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) + { + DirectX11Backend* self; + if (uMsg == WM_NCCREATE) { + auto lpcs = reinterpret_cast(lParam); + self = static_cast(lpcs->lpCreateParams); + self->hWnd = hWnd; + SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast(self)); + } else { + self = reinterpret_cast(GetWindowLongPtr(hWnd, GWLP_USERDATA)); + } + + if (self) { + return self->WndProc(uMsg, wParam, lParam); + } else { + return DefWindowProc(hWnd, uMsg, wParam, lParam); + } + } + + LRESULT WndProc(UINT msg, WPARAM wParam, LPARAM lParam) + { + if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam)) { + return true; + } + + switch (msg) { + case WM_SIZE: { + if (mD3dDevice != nullptr && wParam != SIZE_MINIMIZED) { + CleanupRenderTarget(); + mSwapChain->ResizeBuffers(0, (UINT)LOWORD(lParam), (UINT)HIWORD(lParam), DXGI_FORMAT_UNKNOWN, 0); + CreateRenderTarget(); + } + return 0; + } + + case WM_SYSCOMMAND: { + // Disable ALT application menu + if ((wParam & 0xfff0) == SC_KEYMENU) { + return 0; + } + } break; + + case WM_DESTROY: { + ::PostQuitMessage(0); + return 0; + } + } + return ::DefWindowProc(hWnd, msg, wParam, lParam); + } +}; + +std::unique_ptr RenderingBackend::CreateDx11Backend() +{ + try { + return std::make_unique(); + } catch (std::exception& e) { + return nullptr; + } +} + +#else // ^^ BUILD_CORE_WITH_DX11_BACKEND | BUILD_CORE_WITH_DX11_BACKEND vv + +std::unique_ptr RenderingBackend::CreateDx11Backend() +{ + return nullptr; +} + +#endif diff --git a/app/source/Cplt/Entrypoint/Backend_DirectX12.cpp b/app/source/Cplt/Entrypoint/Backend_DirectX12.cpp new file mode 100644 index 0000000..fd4a531 --- /dev/null +++ b/app/source/Cplt/Entrypoint/Backend_DirectX12.cpp @@ -0,0 +1,470 @@ +#include "Backend.hpp" + +#if BUILD_CORE_WITH_DX12_BACKEND +# include +# include +# include +# include +# include +# include +# include + +constexpr int kNumFramesInFlight = 3; +constexpr int kNumBackBuffers = 3; + +// Forward declare message handler from imgui_impl_win32.cpp +extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); + +class DirectX12Backend : public RenderingBackend +{ +private: + struct FrameContext + { + ID3D12CommandAllocator* CommandAllocator; + UINT64 FenceValue; + }; + + HWND hWnd; + WNDCLASSEX wc; + + FrameContext mFrameContext[kNumFramesInFlight] = {}; + UINT mFrameIndex = 0; + + ID3D12Device* mD3dDevice = nullptr; + ID3D12DescriptorHeap* mD3dRtvDescHeap = nullptr; + ID3D12DescriptorHeap* mD3dSrvDescHeap = nullptr; + ID3D12CommandQueue* mD3dCommandQueue = nullptr; + ID3D12GraphicsCommandList* mD3dCommandList = nullptr; + ID3D12Fence* mFence = nullptr; + HANDLE mFenceEvent = nullptr; + UINT64 mFenceLastSignaledValue = 0; + IDXGISwapChain3* mSwapChain = nullptr; + HANDLE mSwapChainWaitableObject = nullptr; + ID3D12Resource* mMainRenderTargetResource[kNumBackBuffers] = {}; + D3D12_CPU_DESCRIPTOR_HANDLE mMainRenderTargetDescriptor[kNumBackBuffers] = {}; + +public: + DirectX12Backend() + { + ImGui_ImplWin32_EnableDpiAwareness(); + + wc.cbSize = sizeof(WNDCLASSEX); + wc.style = CS_CLASSDC; + wc.lpfnWndProc = &StaticWndProc; + wc.cbClsExtra = 0L; + wc.cbWndExtra = 0L; + wc.hInstance = GetModuleHandle(nullptr); + wc.hIcon = nullptr; + wc.hCursor = nullptr; + wc.hbrBackground = nullptr; + wc.lpszMenuName = nullptr; + wc.lpszClassName = _T("Cplt"); + wc.hIconSm = nullptr; + ::RegisterClassEx(&wc); + + hWnd = ::CreateWindow( + wc.lpszClassName, + _T("Cplt main window"), + WS_OVERLAPPEDWINDOW, + /* x */ 100, + /* y */ 100, + /* window width */ 1280, + /* window height */ 800, + nullptr, + nullptr, + wc.hInstance, + this); + + if (!CreateDeviceD3D()) { + CleanupDeviceD3D(); + ::UnregisterClass(wc.lpszClassName, wc.hInstance); + throw std::runtime_error("Failed to create d3d device."); + } + + ::ShowWindow(hWnd, SW_SHOWDEFAULT); + ::UpdateWindow(hWnd); + + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + + ImGui_ImplWin32_Init(hWnd); + ImGui_ImplDX12_Init(mD3dDevice, kNumFramesInFlight, DXGI_FORMAT_R8G8B8A8_UNORM, mD3dSrvDescHeap, mD3dSrvDescHeap->GetCPUDescriptorHandleForHeapStart(), mD3dSrvDescHeap->GetGPUDescriptorHandleForHeapStart()); + } + + virtual ~DirectX12Backend() + { + WaitForLastSubmittedFrame(); + + // Cleanup + ImGui_ImplDX12_Shutdown(); + ImGui_ImplWin32_Shutdown(); + ImGui::DestroyContext(); + + CleanupDeviceD3D(); + ::DestroyWindow(hWnd); + ::UnregisterClass(wc.lpszClassName, wc.hInstance); + } + + virtual void RunUntilWindowClose(void (*windowContent)()) + { + while (true) { + MSG msg; + bool done = false; + while (::PeekMessage(&msg, nullptr, 0U, 0U, PM_REMOVE)) + { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + if (msg.message == WM_QUIT) { + done = true; + } + } + if (done) break; + + // Start the Dear ImGui frame + ImGui_ImplDX12_NewFrame(); + ImGui_ImplWin32_NewFrame(); + ImGui::NewFrame(); + + windowContent(); + + ImGui::Render(); + + FrameContext* frameCtx = WaitForNextFrameResources(); + UINT backBufferIdx = mSwapChain->GetCurrentBackBufferIndex(); + frameCtx->CommandAllocator->Reset(); + + D3D12_RESOURCE_BARRIER barrier = {}; + barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; + barrier.Transition.pResource = mMainRenderTargetResource[backBufferIdx]; + barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT; + barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET; + mD3dCommandList->Reset(frameCtx->CommandAllocator, nullptr); + mD3dCommandList->ResourceBarrier(1, &barrier); + + const ImVec4 kClearColor = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + const float kClearColorWithAlpha[4] = { kClearColor.x * kClearColor.w, kClearColor.y * kClearColor.w, kClearColor.z * kClearColor.w, kClearColor.w }; + mD3dCommandList->ClearRenderTargetView(mMainRenderTargetDescriptor[backBufferIdx], kClearColorWithAlpha, 0, nullptr); + mD3dCommandList->OMSetRenderTargets(1, &mMainRenderTargetDescriptor[backBufferIdx], FALSE, nullptr); + mD3dCommandList->SetDescriptorHeaps(1, &mD3dSrvDescHeap); + ImGui_ImplDX12_RenderDrawData(ImGui::GetDrawData(), mD3dCommandList); + barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET; + barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT; + mD3dCommandList->ResourceBarrier(1, &barrier); + mD3dCommandList->Close(); + + mD3dCommandQueue->ExecuteCommandLists(1, (ID3D12CommandList* const*)&mD3dCommandList); + + mSwapChain->Present(1, 0); // Present with vsync + + UINT64 fenceValue = mFenceLastSignaledValue + 1; + mD3dCommandQueue->Signal(mFence, fenceValue); + mFenceLastSignaledValue = fenceValue; + frameCtx->FenceValue = fenceValue; + } + } + +private: + bool CreateDeviceD3D() + { + // Setup swap chain + DXGI_SWAP_CHAIN_DESC1 sd; + { + ZeroMemory(&sd, sizeof(sd)); + sd.BufferCount = kNumBackBuffers; + sd.Width = 0; + sd.Height = 0; + sd.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + sd.Flags = DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT; + sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + sd.SampleDesc.Count = 1; + sd.SampleDesc.Quality = 0; + sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; + sd.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED; + sd.Scaling = DXGI_SCALING_STRETCH; + sd.Stereo = FALSE; + } + + // Create device + D3D_FEATURE_LEVEL featureLevel = D3D_FEATURE_LEVEL_11_0; + if (D3D12CreateDevice(nullptr, featureLevel, IID_PPV_ARGS(&mD3dDevice)) != S_OK) { + return false; + } + + { + D3D12_DESCRIPTOR_HEAP_DESC desc = {}; + desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; + desc.NumDescriptors = kNumBackBuffers; + desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; + desc.NodeMask = 1; + if (mD3dDevice->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&mD3dRtvDescHeap)) != S_OK) { + return false; + } + + SIZE_T rtvDescriptorSize = mD3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); + D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = mD3dRtvDescHeap->GetCPUDescriptorHandleForHeapStart(); + for (UINT i = 0; i < kNumBackBuffers; i++) { + mMainRenderTargetDescriptor[i] = rtvHandle; + rtvHandle.ptr += rtvDescriptorSize; + } + } + + { + D3D12_DESCRIPTOR_HEAP_DESC desc = {}; + desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; + desc.NumDescriptors = 1; + desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; + if (mD3dDevice->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&mD3dSrvDescHeap)) != S_OK) { + return false; + } + } + + { + D3D12_COMMAND_QUEUE_DESC desc = {}; + desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; + desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; + desc.NodeMask = 1; + if (mD3dDevice->CreateCommandQueue(&desc, IID_PPV_ARGS(&mD3dCommandQueue)) != S_OK) { + return false; + } + } + + for (UINT i = 0; i < kNumFramesInFlight; i++) { + if (mD3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&mFrameContext[i].CommandAllocator)) != S_OK) { + return false; + } + } + + if (mD3dDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, mFrameContext[0].CommandAllocator, nullptr, IID_PPV_ARGS(&mD3dCommandList)) != S_OK || + mD3dCommandList->Close() != S_OK) + { + return false; + } + + if (mD3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&mFence)) != S_OK) return false; + + mFenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); + if (mFenceEvent == nullptr) return false; + + { + IDXGIFactory4* dxgiFactory = nullptr; + IDXGISwapChain1* swapChain1 = nullptr; + if (CreateDXGIFactory1(IID_PPV_ARGS(&dxgiFactory)) != S_OK) + return false; + if (dxgiFactory->CreateSwapChainForHwnd(mD3dCommandQueue, hWnd, &sd, nullptr, nullptr, &swapChain1) != S_OK) + return false; + if (swapChain1->QueryInterface(IID_PPV_ARGS(&mSwapChain)) != S_OK) + return false; + swapChain1->Release(); + dxgiFactory->Release(); + mSwapChain->SetMaximumFrameLatency(kNumBackBuffers); + mSwapChainWaitableObject = mSwapChain->GetFrameLatencyWaitableObject(); + } + + CreateRenderTarget(); + return true; + } + + void CleanupDeviceD3D() + { + CleanupRenderTarget(); + if (mSwapChain) { + mSwapChain->Release(); + mSwapChain = nullptr; + } + if (mSwapChainWaitableObject != nullptr) { + CloseHandle(mSwapChainWaitableObject); + } + for (UINT i = 0; i < kNumFramesInFlight; i++) + if (mFrameContext[i].CommandAllocator) { + mFrameContext[i].CommandAllocator->Release(); + mFrameContext[i].CommandAllocator = nullptr; + } + if (mD3dCommandQueue) { + mD3dCommandQueue->Release(); + mD3dCommandQueue = nullptr; + } + if (mD3dCommandList) { + mD3dCommandList->Release(); + mD3dCommandList = nullptr; + } + if (mD3dRtvDescHeap) { + mD3dRtvDescHeap->Release(); + mD3dRtvDescHeap = nullptr; + } + if (mD3dSrvDescHeap) { + mD3dSrvDescHeap->Release(); + mD3dSrvDescHeap = nullptr; + } + if (mFence) { + mFence->Release(); + mFence = nullptr; + } + if (mFenceEvent) { + CloseHandle(mFenceEvent); + mFenceEvent = nullptr; + } + if (mD3dDevice) { + mD3dDevice->Release(); + mD3dDevice = nullptr; + } + } + + void CreateRenderTarget() + { + for (UINT i = 0; i < kNumBackBuffers; i++) + { + ID3D12Resource* pBackBuffer = nullptr; + mSwapChain->GetBuffer(i, IID_PPV_ARGS(&pBackBuffer)); + mD3dDevice->CreateRenderTargetView(pBackBuffer, nullptr, mMainRenderTargetDescriptor[i]); + mMainRenderTargetResource[i] = pBackBuffer; + } + } + + void CleanupRenderTarget() + { + WaitForLastSubmittedFrame(); + + for (UINT i = 0; i < kNumBackBuffers; i++) + if (mMainRenderTargetResource[i]) { + mMainRenderTargetResource[i]->Release(); + mMainRenderTargetResource[i] = nullptr; + } + } + + void WaitForLastSubmittedFrame() + { + FrameContext* frameCtx = &mFrameContext[mFrameIndex % kNumFramesInFlight]; + + UINT64 fenceValue = frameCtx->FenceValue; + if (fenceValue == 0) + return; // No fence was signaled + + frameCtx->FenceValue = 0; + if (mFence->GetCompletedValue() >= fenceValue) + return; + + mFence->SetEventOnCompletion(fenceValue, mFenceEvent); + WaitForSingleObject(mFenceEvent, INFINITE); + } + + FrameContext* WaitForNextFrameResources() + { + UINT nextFrameIndex = mFrameIndex + 1; + mFrameIndex = nextFrameIndex; + + HANDLE waitableObjects[] = { mSwapChainWaitableObject, nullptr }; + DWORD numWaitableObjects = 1; + + FrameContext* frameCtx = &mFrameContext[nextFrameIndex % kNumFramesInFlight]; + UINT64 fenceValue = frameCtx->FenceValue; + if (fenceValue != 0) // means no fence was signaled + { + frameCtx->FenceValue = 0; + mFence->SetEventOnCompletion(fenceValue, mFenceEvent); + waitableObjects[1] = mFenceEvent; + numWaitableObjects = 2; + } + + WaitForMultipleObjects(numWaitableObjects, waitableObjects, TRUE, INFINITE); + + return frameCtx; + } + + void ResizeSwapChain(int width, int height) + { + DXGI_SWAP_CHAIN_DESC1 sd; + mSwapChain->GetDesc1(&sd); + sd.Width = width; + sd.Height = height; + + IDXGIFactory4* dxgiFactory = nullptr; + mSwapChain->GetParent(IID_PPV_ARGS(&dxgiFactory)); + + mSwapChain->Release(); + CloseHandle(mSwapChainWaitableObject); + + IDXGISwapChain1* swapChain1 = nullptr; + dxgiFactory->CreateSwapChainForHwnd(mD3dCommandQueue, hWnd, &sd, nullptr, nullptr, &swapChain1); + swapChain1->QueryInterface(IID_PPV_ARGS(&mSwapChain)); + swapChain1->Release(); + dxgiFactory->Release(); + + mSwapChain->SetMaximumFrameLatency(kNumBackBuffers); + + mSwapChainWaitableObject = mSwapChain->GetFrameLatencyWaitableObject(); + assert(mSwapChainWaitableObject != nullptr); + } + + static LRESULT CALLBACK StaticWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) + { + DirectX12Backend* self; + if (uMsg == WM_NCCREATE) { + auto lpcs = reinterpret_cast(lParam); + self = static_cast(lpcs->lpCreateParams); + self->hWnd = hWnd; + SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast(self)); + } else { + self = reinterpret_cast(GetWindowLongPtr(hWnd, GWLP_USERDATA)); + } + + if (self) { + return self->WndProc(uMsg, wParam, lParam); + } else { + return DefWindowProc(hWnd, uMsg, wParam, lParam); + } + } + + LRESULT WndProc(UINT msg, WPARAM wParam, LPARAM lParam) + { + if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam)) { + return true; + } + + switch (msg) { + case WM_SIZE: { + if (mD3dDevice != nullptr && wParam != SIZE_MINIMIZED) { + WaitForLastSubmittedFrame(); + ImGui_ImplDX12_InvalidateDeviceObjects(); + CleanupRenderTarget(); + ResizeSwapChain((UINT)LOWORD(lParam), (UINT)HIWORD(lParam)); + CreateRenderTarget(); + ImGui_ImplDX12_CreateDeviceObjects(); + } + return 0; + } + + case WM_SYSCOMMAND: { + // Disable ALT application menu + if ((wParam & 0xfff0) == SC_KEYMENU) { + return 0; + } + } break; + + case WM_DESTROY: { + ::PostQuitMessage(0); + return 0; + } + } + return ::DefWindowProc(hWnd, msg, wParam, lParam); + } +}; + +std::unique_ptr RenderingBackend::CreateDx12Backend() +{ + try { + return std::make_unique(); + } catch (std::exception& e) { + return nullptr; + } +} + +#else // ^^ BUILD_CORE_WITH_DX12_BACKEND | BUILD_CORE_WITH_DX12_BACKEND vv + +std::unique_ptr RenderingBackend::CreateDx12Backend() +{ + return nullptr; +} + +#endif diff --git a/app/source/Cplt/Entrypoint/Backend_Metal.mm b/app/source/Cplt/Entrypoint/Backend_Metal.mm new file mode 100644 index 0000000..276bef2 --- /dev/null +++ b/app/source/Cplt/Entrypoint/Backend_Metal.mm @@ -0,0 +1,40 @@ +#include "Backend.hpp" + +#if BUILD_CORE_WITH_METAL_BACKEND + +class MetalBackend : public RenderingBackend +{ +public: + MetalBackend() + { + // TODO + } + + virtual ~MetalBackend() + { + // TODO + } + + virtual void RunUntilWindowClose(void (*windowContent)()) + { + // TODO + } +}; + +std::unique_ptr RenderingBackend::CreateMetalBackend() +{ + try { + return std::make_unique(); + } catch (std::exception& e) { + return nullptr; + } +} + +#else // ^^ BUILD_CORE_WITH_METAL_BACKEND | BUILD_CORE_WITH_METAL_BACKEND vv + +std::unique_ptr RenderingBackend::CreateMetalBackend() +{ + return nullptr; +} + +#endif diff --git a/app/source/Cplt/Entrypoint/Backend_OpenGL2.cpp b/app/source/Cplt/Entrypoint/Backend_OpenGL2.cpp new file mode 100644 index 0000000..0f20997 --- /dev/null +++ b/app/source/Cplt/Entrypoint/Backend_OpenGL2.cpp @@ -0,0 +1,106 @@ +#include + +#if BUILD_CORE_WITH_OPENGL2_BACKEND +# include + +# include +# include +# include +# include +# include +# include + +# define IMGUI_IMPL_OPENGL_LOADER_CUSTOM +# include + +class OpenGL2Backend : public RenderingBackend +{ +private: + GLFWwindow* mWindow; + +public: + OpenGL2Backend() + { + glfwSetErrorCallback(&GlfwErrorCallback); + if (!glfwInit()) { + throw std::runtime_error("Failed to initialize GLFW."); + } + + mWindow = glfwCreateWindow(1280, 720, "Cplt", nullptr, nullptr); + if (mWindow == nullptr) { + throw std::runtime_error("Failed to create GLFW window."); + } + glfwMakeContextCurrent(mWindow); + glfwSwapInterval(1); // Enable vsync + + if (gladLoadGLLoader((GLADloadproc)glfwGetProcAddress) == 0) { + throw std::runtime_error("Failed to initialize OpenGL."); + } + + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + + ImGui_ImplGlfw_InitForOpenGL(mWindow, true); + ImGui_ImplOpenGL2_Init(); + } + + virtual ~OpenGL2Backend() + { + ImGui_ImplOpenGL2_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + ImGui::DestroyContext(); + + glfwDestroyWindow(mWindow); + glfwTerminate(); + } + + virtual void RunUntilWindowClose(void (*windowContent)()) + { + while (!glfwWindowShouldClose(mWindow)) { + glfwPollEvents(); + + ImGui_ImplOpenGL2_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + + windowContent(); + + int displayWidth, displayHeight; + glfwGetFramebufferSize(mWindow, &displayWidth, &displayHeight); + glViewport(0, 0, displayWidth, displayHeight); + + const ImVec4 kClearColor = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + glClearColor(kClearColor.x * kClearColor.w, kClearColor.y * kClearColor.w, kClearColor.z * kClearColor.w, kClearColor.w); + glClear(GL_COLOR_BUFFER_BIT); + + ImGui::Render(); + ImGui_ImplOpenGL2_RenderDrawData(ImGui::GetDrawData()); + + glfwMakeContextCurrent(mWindow); + glfwSwapBuffers(mWindow); + } + } + + static void GlfwErrorCallback(int errorCode, const char* message) + { + std::cerr << "GLFW Error " << errorCode << ": " << message << "\n"; + } +}; + +std::unique_ptr RenderingBackend::CreateOpenGL2Backend() +{ + try { + return std::make_unique(); + } catch (std::exception& e) { + return nullptr; + } +} + +#else // ^^ BUILD_CORE_WITH_OPENGL2_BACKEND | !BUILD_CORE_WITH_OPENGL2_BACKEND vv + +std::unique_ptr RenderingBackend::CreateOpenGL2Backend() +{ + return nullptr; +} + +#endif diff --git a/app/source/Cplt/Entrypoint/Backend_OpenGL3.cpp b/app/source/Cplt/Entrypoint/Backend_OpenGL3.cpp new file mode 100644 index 0000000..28a34ca --- /dev/null +++ b/app/source/Cplt/Entrypoint/Backend_OpenGL3.cpp @@ -0,0 +1,121 @@ +#include + +#if BUILD_CORE_WITH_OPENGL3_BACKEND +# include + +# include +# include +# include +# include +# include +# include + +# define IMGUI_IMPL_OPENGL_LOADER_CUSTOM +# include + +class OpenGL3Backend : public RenderingBackend +{ +private: + GLFWwindow* mWindow; + +public: + OpenGL3Backend() + { + glfwSetErrorCallback(&GlfwErrorCallback); + if (!glfwInit()) { + throw std::runtime_error("Failed to initialize GLFW."); + } + +# if defined(__APPLE__) + // GL 3.2 + GLSL 150 + const char* glslVersion = "#version 150"; + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac +# else + // GL 3.0 + GLSL 130 + const char* glslVersion = "#version 130"; + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); + // glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only + // glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // 3.0+ only +# endif + + mWindow = glfwCreateWindow(1280, 720, "Cplt", nullptr, nullptr); + if (mWindow == nullptr) { + throw std::runtime_error("Failed to create GLFW window."); + } + glfwMakeContextCurrent(mWindow); + glfwSwapInterval(1); // Enable vsync + + if (gladLoadGLLoader((GLADloadproc)glfwGetProcAddress) == 0) { + throw std::runtime_error("Failed to initialize OpenGL."); + } + + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + + ImGui_ImplGlfw_InitForOpenGL(mWindow, true); + ImGui_ImplOpenGL3_Init(glslVersion); + } + + virtual ~OpenGL3Backend() + { + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + ImGui::DestroyContext(); + + glfwDestroyWindow(mWindow); + glfwTerminate(); + } + + virtual void RunUntilWindowClose(void (*windowContent)()) + { + while (!glfwWindowShouldClose(mWindow)) { + glfwPollEvents(); + + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + + windowContent(); + + int displayWidth, displayHeight; + glfwGetFramebufferSize(mWindow, &displayWidth, &displayHeight); + glViewport(0, 0, displayWidth, displayHeight); + + const ImVec4 kClearColor = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + glClearColor(kClearColor.x * kClearColor.w, kClearColor.y * kClearColor.w, kClearColor.z * kClearColor.w, kClearColor.w); + glClear(GL_COLOR_BUFFER_BIT); + + ImGui::Render(); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + + glfwSwapBuffers(mWindow); + } + } + + static void GlfwErrorCallback(int errorCode, const char* message) + { + std::cerr << "GLFW Error " << errorCode << ": " << message << "\n"; + } +}; + +std::unique_ptr RenderingBackend::CreateOpenGL3Backend() +{ + try { + return std::make_unique(); + } catch (std::exception& e) { + return nullptr; + } +} + +#else // ^^ BUILD_CORE_WITH_OPENGL3_BACKEND | !BUILD_CORE_WITH_OPENGL3_BACKEND vv + +std::unique_ptr RenderingBackend::CreateOpenGL3Backend() +{ + return nullptr; +} + +#endif diff --git a/app/source/Cplt/Entrypoint/Backend_Vulkan.cpp b/app/source/Cplt/Entrypoint/Backend_Vulkan.cpp new file mode 100644 index 0000000..280a82b --- /dev/null +++ b/app/source/Cplt/Entrypoint/Backend_Vulkan.cpp @@ -0,0 +1,438 @@ +#include + +#if BUILD_CORE_WITH_VULKAN_BACKEND +# include +# include + +# define GLFW_INCLUDE_NONE +# define GLFW_INCLUDE_VULKAN +# include + +# include +# include +# include + +class VulkanBackend : public RenderingBackend +{ +private: + GLFWwindow* mWindow; + + VkAllocationCallbacks* mAllocator = NULL; + VkInstance mInstance = VK_NULL_HANDLE; + VkPhysicalDevice mPhysicalDevice = VK_NULL_HANDLE; + VkDevice mDevice = VK_NULL_HANDLE; + uint32_t mQueueFamily = (uint32_t)-1; + VkQueue mQueue = VK_NULL_HANDLE; + VkDebugReportCallbackEXT mDebugReport = VK_NULL_HANDLE; + VkPipelineCache mPipelineCache = VK_NULL_HANDLE; + VkDescriptorPool mDescriptorPool = VK_NULL_HANDLE; + + ImGui_ImplVulkanH_Window mMainWindowData; + int mMinImageCount = 2; + bool mSwapChainRebuild = false; + +public: + VulkanBackend() + { + glfwSetErrorCallback(&GlfwErrorCallback); + if (!glfwInit()) { + throw std::runtime_error("Failed to initialize GLFW."); + } + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + mWindow = glfwCreateWindow(1280, 720, "Cplt", nullptr, nullptr); + if (mWindow == nullptr) { + throw std::runtime_error("Failed to create GLFW window."); + } + + if (!glfwVulkanSupported()) { + throw std::runtime_error("GLFW reports vulkan not supported."); + } + + uint32_t extensionsCount = 0; + const char** extensions = glfwGetRequiredInstanceExtensions(&extensionsCount); + SetupVulkan(extensions, extensionsCount); + + // Create window surface + VkSurfaceKHR surface; + VkResult err = glfwCreateWindowSurface(mInstance, mWindow, mAllocator, &surface); + CheckVkResults(err); + + // Create framebuffers + int w, h; + glfwGetFramebufferSize(mWindow, &w, &h); + SetupVulkanWindow(&mMainWindowData, surface, w, h); + + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + + ImGui_ImplGlfw_InitForVulkan(mWindow, true); + ImGui_ImplVulkan_InitInfo init_info = {}; + init_info.Instance = mInstance; + init_info.PhysicalDevice = mPhysicalDevice; + init_info.Device = mDevice; + init_info.QueueFamily = mQueueFamily; + init_info.Queue = mQueue; + init_info.PipelineCache = mPipelineCache; + init_info.DescriptorPool = mDescriptorPool; + init_info.Allocator = mAllocator; + init_info.MinImageCount = mMinImageCount; + init_info.ImageCount = mMainWindowData.ImageCount; + init_info.CheckVkResultFn = CheckVkResults; + ImGui_ImplVulkan_Init(&init_info, mMainWindowData.RenderPass); + } + + virtual ~VulkanBackend() + { + auto err = vkDeviceWaitIdle(mDevice); + CheckVkResults(err); + ImGui_ImplVulkan_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + ImGui::DestroyContext(); + + CleanupVulkanWindow(); + CleanupVulkan(); + + glfwDestroyWindow(mWindow); + glfwTerminate(); + } + + virtual void RunUntilWindowClose(void (*windowContent)()) override + { + // Upload Fonts + { + // Use any command queue + VkCommandPool commandPool = mMainWindowData.Frames[mMainWindowData.FrameIndex].CommandPool; + VkCommandBuffer commandBuffer = mMainWindowData.Frames[mMainWindowData.FrameIndex].CommandBuffer; + + CheckVkResults(vkResetCommandPool(mDevice, commandPool, 0)); + VkCommandBufferBeginInfo beginInfo = {}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags |= VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + CheckVkResults(vkBeginCommandBuffer(commandBuffer, &beginInfo)); + + ImGui_ImplVulkan_CreateFontsTexture(commandBuffer); + + VkSubmitInfo endInfo = {}; + endInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + endInfo.commandBufferCount = 1; + endInfo.pCommandBuffers = &commandBuffer; + CheckVkResults(vkEndCommandBuffer(commandBuffer)); + CheckVkResults(vkQueueSubmit(mQueue, 1, &endInfo, VK_NULL_HANDLE)); + + CheckVkResults(vkDeviceWaitIdle(mDevice)); + ImGui_ImplVulkan_DestroyFontUploadObjects(); + } + + while (!glfwWindowShouldClose(mWindow)) { + glfwPollEvents(); + + // Resize swap chain? + if (mSwapChainRebuild) { + int width, height; + glfwGetFramebufferSize(mWindow, &width, &height); + if (width > 0 && height > 0) { + ImGui_ImplVulkan_SetMinImageCount(mMinImageCount); + ImGui_ImplVulkanH_CreateOrResizeWindow(mInstance, mPhysicalDevice, mDevice, &mMainWindowData, mQueueFamily, mAllocator, width, height, mMinImageCount); + mMainWindowData.FrameIndex = 0; + mSwapChainRebuild = false; + } + } + + // Start the Dear ImGui frame + ImGui_ImplVulkan_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + + windowContent(); + + ImGui::Render(); + ImDrawData* drawData = ImGui::GetDrawData(); + const bool isMinimized = (drawData->DisplaySize.x <= 0.0f || drawData->DisplaySize.y <= 0.0f); + if (!isMinimized) { + const ImVec4 kClearColor = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + mMainWindowData.ClearValue.color.float32[0] = kClearColor.x * kClearColor.w; + mMainWindowData.ClearValue.color.float32[1] = kClearColor.y * kClearColor.w; + mMainWindowData.ClearValue.color.float32[2] = kClearColor.z * kClearColor.w; + mMainWindowData.ClearValue.color.float32[3] = kClearColor.w; + FrameRender(&mMainWindowData, drawData); + FramePresent(&mMainWindowData); + } + } + } + +private: + void SetupVulkan(const char** extensions, uint32_t extensions_count) + { + VkResult err; + + // Create Vulkan Instance + { + VkInstanceCreateInfo createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.enabledExtensionCount = extensions_count; + createInfo.ppEnabledExtensionNames = extensions; + // Create Vulkan Instance without any debug feature + err = vkCreateInstance(&createInfo, mAllocator, &mInstance); + CheckVkResults(err); + } + + // Select GPU + { + uint32_t gpuCount; + err = vkEnumeratePhysicalDevices(mInstance, &gpuCount, NULL); + CheckVkResults(err); + IM_ASSERT(gpuCount > 0); + + VkPhysicalDevice* gpus = (VkPhysicalDevice*)malloc(sizeof(VkPhysicalDevice) * gpuCount); + err = vkEnumeratePhysicalDevices(mInstance, &gpuCount, gpus); + CheckVkResults(err); + + // If a number >1 of GPUs got reported, find discrete GPU if present, or use first one available. This covers + // most common cases (multi-gpu/integrated+dedicated graphics). Handling more complicated setups (multiple + // dedicated GPUs) is out of scope of this sample. + int useGpu = 0; + for (int i = 0; i < (int)gpuCount; i++) + { + VkPhysicalDeviceProperties properties; + vkGetPhysicalDeviceProperties(gpus[i], &properties); + if (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) + { + useGpu = i; + break; + } + } + + mPhysicalDevice = gpus[useGpu]; + free(gpus); + } + + // Select graphics queue family + { + uint32_t count; + vkGetPhysicalDeviceQueueFamilyProperties(mPhysicalDevice, &count, NULL); + + auto queues = std::make_unique(count); + vkGetPhysicalDeviceQueueFamilyProperties(mPhysicalDevice, &count, queues.get()); + for (uint32_t i = 0; i < count; i++) { + if (queues[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) + { + mQueueFamily = i; + break; + } + } + + IM_ASSERT(mQueueFamily != (uint32_t)-1); + } + + // Create Logical Device (with 1 queue) + { + int deviceExtensionCount = 1; + const char* deviceExtensions[] = { "VK_KHR_swapchain" }; + const float queuePriority[] = { 1.0f }; + VkDeviceQueueCreateInfo queue_info[1] = {}; + queue_info[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queue_info[0].queueFamilyIndex = mQueueFamily; + queue_info[0].queueCount = 1; + queue_info[0].pQueuePriorities = queuePriority; + VkDeviceCreateInfo createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + createInfo.queueCreateInfoCount = sizeof(queue_info) / sizeof(queue_info[0]); + createInfo.pQueueCreateInfos = queue_info; + createInfo.enabledExtensionCount = deviceExtensionCount; + createInfo.ppEnabledExtensionNames = deviceExtensions; + err = vkCreateDevice(mPhysicalDevice, &createInfo, mAllocator, &mDevice); + CheckVkResults(err); + vkGetDeviceQueue(mDevice, mQueueFamily, 0, &mQueue); + } + + // Create Descriptor Pool + { + VkDescriptorPoolSize poolSizes[] = { + { VK_DESCRIPTOR_TYPE_SAMPLER, 1000 }, + { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000 }, + { VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000 }, + { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1000 }, + { VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1000 }, + { VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1000 }, + { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000 }, + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1000 }, + { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1000 }, + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1000 }, + { VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000 } + }; + VkDescriptorPoolCreateInfo poolInfo = {}; + poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + poolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; + poolInfo.maxSets = 1000 * IM_ARRAYSIZE(poolSizes); + poolInfo.poolSizeCount = (uint32_t)IM_ARRAYSIZE(poolSizes); + poolInfo.pPoolSizes = poolSizes; + err = vkCreateDescriptorPool(mDevice, &poolInfo, mAllocator, &mDescriptorPool); + CheckVkResults(err); + } + } + + void SetupVulkanWindow(ImGui_ImplVulkanH_Window* wd, VkSurfaceKHR surface, int width, int height) + { + wd->Surface = surface; + + // Check for WSI support + VkBool32 res; + vkGetPhysicalDeviceSurfaceSupportKHR(mPhysicalDevice, mQueueFamily, wd->Surface, &res); + if (res != VK_TRUE) { + throw "Error no WSI support on physical device 0."; + } + + // Select Surface Format + const VkFormat requestSurfaceImageFormat[] = { VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_B8G8R8_UNORM, VK_FORMAT_R8G8B8_UNORM }; + const VkColorSpaceKHR requestSurfaceColorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR; + wd->SurfaceFormat = ImGui_ImplVulkanH_SelectSurfaceFormat(mPhysicalDevice, wd->Surface, requestSurfaceImageFormat, (size_t)IM_ARRAYSIZE(requestSurfaceImageFormat), requestSurfaceColorSpace); + + // Select Present Mode + VkPresentModeKHR present_modes[] = { VK_PRESENT_MODE_FIFO_KHR }; + wd->PresentMode = ImGui_ImplVulkanH_SelectPresentMode(mPhysicalDevice, wd->Surface, &present_modes[0], IM_ARRAYSIZE(present_modes)); + + // Create SwapChain, RenderPass, Framebuffer, etc. + IM_ASSERT(mMinImageCount >= 2); + ImGui_ImplVulkanH_CreateOrResizeWindow(mInstance, mPhysicalDevice, mDevice, wd, mQueueFamily, mAllocator, width, height, mMinImageCount); + } + + void FrameRender(ImGui_ImplVulkanH_Window* wd, ImDrawData* drawData) + { + VkResult err; + + VkSemaphore imageAcquiredSemaphore = wd->FrameSemaphores[wd->SemaphoreIndex].ImageAcquiredSemaphore; + VkSemaphore renderCompleteSemaphore = wd->FrameSemaphores[wd->SemaphoreIndex].RenderCompleteSemaphore; + err = vkAcquireNextImageKHR(mDevice, wd->Swapchain, UINT64_MAX, imageAcquiredSemaphore, VK_NULL_HANDLE, &wd->FrameIndex); + if (err == VK_ERROR_OUT_OF_DATE_KHR || err == VK_SUBOPTIMAL_KHR) { + mSwapChainRebuild = true; + return; + } + CheckVkResults(err); + + ImGui_ImplVulkanH_Frame* fd = &wd->Frames[wd->FrameIndex]; + { + err = vkWaitForFences(mDevice, 1, &fd->Fence, VK_TRUE, UINT64_MAX); // wait indefinitely instead of periodically checking + CheckVkResults(err); + + err = vkResetFences(mDevice, 1, &fd->Fence); + CheckVkResults(err); + } + { + err = vkResetCommandPool(mDevice, fd->CommandPool, 0); + CheckVkResults(err); + VkCommandBufferBeginInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + info.flags |= VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + err = vkBeginCommandBuffer(fd->CommandBuffer, &info); + CheckVkResults(err); + } + { + VkRenderPassBeginInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + info.renderPass = wd->RenderPass; + info.framebuffer = fd->Framebuffer; + info.renderArea.extent.width = wd->Width; + info.renderArea.extent.height = wd->Height; + info.clearValueCount = 1; + info.pClearValues = &wd->ClearValue; + vkCmdBeginRenderPass(fd->CommandBuffer, &info, VK_SUBPASS_CONTENTS_INLINE); + } + + // Record dear imgui primitives into command buffer + ImGui_ImplVulkan_RenderDrawData(drawData, fd->CommandBuffer); + + // Submit command buffer + vkCmdEndRenderPass(fd->CommandBuffer); + { + VkPipelineStageFlags wait_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + VkSubmitInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + info.waitSemaphoreCount = 1; + info.pWaitSemaphores = &imageAcquiredSemaphore; + info.pWaitDstStageMask = &wait_stage; + info.commandBufferCount = 1; + info.pCommandBuffers = &fd->CommandBuffer; + info.signalSemaphoreCount = 1; + info.pSignalSemaphores = &renderCompleteSemaphore; + + err = vkEndCommandBuffer(fd->CommandBuffer); + CheckVkResults(err); + err = vkQueueSubmit(mQueue, 1, &info, fd->Fence); + CheckVkResults(err); + } + } + + void FramePresent(ImGui_ImplVulkanH_Window* wd) + { + if (mSwapChainRebuild) { + return; + } + + VkSemaphore renderCompleteSemaphore = wd->FrameSemaphores[wd->SemaphoreIndex].RenderCompleteSemaphore; + VkPresentInfoKHR info = {}; + info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + info.waitSemaphoreCount = 1; + info.pWaitSemaphores = &renderCompleteSemaphore; + info.swapchainCount = 1; + info.pSwapchains = &wd->Swapchain; + info.pImageIndices = &wd->FrameIndex; + VkResult err = vkQueuePresentKHR(mQueue, &info); + if (err == VK_ERROR_OUT_OF_DATE_KHR || err == VK_SUBOPTIMAL_KHR) { + mSwapChainRebuild = true; + return; + } + CheckVkResults(err); + wd->SemaphoreIndex = (wd->SemaphoreIndex + 1) % wd->ImageCount; // Now we can use the next set of semaphores + } + + void CleanupVulkan() + { + vkDestroyDescriptorPool(mDevice, mDescriptorPool, mAllocator); + + vkDestroyDevice(mDevice, mAllocator); + vkDestroyInstance(mInstance, mAllocator); + } + + void CleanupVulkanWindow() + { + ImGui_ImplVulkanH_DestroyWindow(mInstance, mDevice, &mMainWindowData, mAllocator); + } + + static void CheckVkResults(VkResult err) + { + if (err == 0) return; + + std::string message; + message += "Vulkan error: VkResult = "; + message += err; + + if (err < 0) { + throw std::runtime_error(message); + } else { + std::cerr << message << '\n'; + } + } + static void GlfwErrorCallback(int errorCode, const char* message) + { + std::cerr << "GLFW Error " << errorCode << ": " << message << "\n"; + } +}; + +std::unique_ptr RenderingBackend::CreateVulkanBackend() +{ + try { + return std::make_unique(); + } catch (std::exception& e) { + return nullptr; + } +} + +#else // ^^ BUILD_CORE_WITH_VULKAN_BACKEND | ~BUILD_CORE_WITH_VULKAN_BACKEND vv + +std::unique_ptr RenderingBackend::CreateVulkanBackend() +{ + return nullptr; +} + +#endif diff --git a/app/source/Cplt/Entrypoint/main.cpp b/app/source/Cplt/Entrypoint/main.cpp new file mode 100644 index 0000000..8f67d32 --- /dev/null +++ b/app/source/Cplt/Entrypoint/main.cpp @@ -0,0 +1,163 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; +using namespace std::literals::string_literals; +using namespace std::literals::string_view_literals; + +static std::unique_ptr CreateDefaultBackend() +{ +#if defined(_WIN32) +# if BUILD_CORE_WITH_DX12_BACKEND + if (auto backend = RenderingBackend::CreateDx12Backend()) { + return backend; + } +# endif +# if BUILD_CORE_WITH_DX11_BACKEND + if (auto backend = RenderingBackend::CreateDx11Backend()) { + return backend; + } +# endif +# if BUILD_CORE_WITH_VULKAN_BACKEND + if (auto backend = RenderingBackend::CreateVulkanBackend()) { + return backend; + } +# endif +# if BUILD_CORE_WITH_OPENGL3_BACKEND + if (auto backend = RenderingBackend::CreateOpenGL3Backend()) { + return backend; + } +# endif +# if BUILD_CORE_WITH_OPENGL2_BACKEND + if (auto backend = RenderingBackend::CreateOpenGL2Backend()) { + return backend; + } +# endif +#elif defined(__APPLE__) + // We currently only support using metal on macos + return RenderingBackend::CreateMetalBackend(); +#elif defined(__linux__) +# if BUILD_CORE_WITH_VULKAN_BACKEND + if (auto backend = RenderingBackend::CreateVulkanBackend()) { + return backend; + } +# endif +# if BUILD_CORE_WITH_OPENGL3_BACKEND + if (auto backend = RenderingBackend::CreateOpenGL3Backend()) { + return backend; + } +# endif +# if BUILD_CORE_WITH_OPENGL2_BACKEND + if (auto backend = RenderingBackend::CreateOpenGL2Backend()) { + return backend; + } +# endif +#endif + + return nullptr; +} + +static std::unique_ptr CreateBackend(std::string_view option) +{ + if (option == "default") { + return CreateDefaultBackend(); + } else if (option == "opengl2") { + return RenderingBackend::CreateOpenGL2Backend(); + } else if (option == "opengl3") { + return RenderingBackend::CreateOpenGL3Backend(); + } else if (option == "vulkan") { + return RenderingBackend::CreateVulkanBackend(); + } else if (option == "dx11") { + return RenderingBackend::CreateDx11Backend(); + } else if (option == "dx12") { + return RenderingBackend::CreateDx12Backend(); + } else if (option == "metal") { + return RenderingBackend::CreateMetalBackend(); + } else { + std::string message; + message += "Unknown backend '"; + message += option; + message += "'.\n"; + throw std::runtime_error(message); + } +} + +#ifdef DOCTEST_CONFIG_DISABLE +int main(int argc, char* argv[]) +{ + argparse::ArgumentParser parser; + parser.add_argument("--global-data-directory") + .help("Directory in which global data (such as recently used projects) are saved to. Use 'default' to use the default directory on each platform.") + .default_value("default"s); + parser.add_argument("--rendering-backend") + .help("Which rendering backend to use. If equals 'default', the preferred API for each platform will be used") + .default_value("default"s); + + try { + parser.parse_args(argc, argv); + } catch (const std::runtime_error& error) { + std::cout << error.what() << '\n'; + std::cout << parser; + return -1; + } + + auto backendOption = parser.get("--rendering-backend"); + auto backend = CreateBackend(backendOption); + + auto& io = ImGui::GetIO(); + + // Disable saving window positions + io.IniFilename = nullptr; + // Disable log (dump widget tree) file, we don't trigger it but just to be safe + io.LogFilename = nullptr; + + // Light mode because all major OS's default theme is white + // TODO follow system theme + ImGui::StyleColorsLight(); + + // Configure default fonts + { + // Includes latin alphabet, although for some reason smaller than if rendered using 18 point NotoSans regular + io.Fonts->AddFontFromFileTTF("fonts/NotoSansSC-Regular.otf", 18, nullptr, io.Fonts->GetGlyphRangesChineseFull()); + + ImWchar iconRanges[] = { ICON_MIN_FA, ICON_MAX_FA }; + ImFontConfig config; + config.MergeMode = true; + io.Fonts->AddFontFromFileTTF("fonts/FontAwesome5-Solid.otf", 14, &config, iconRanges); + } + + auto dataDirOption = parser.get("--global-data-directory"); + if (dataDirOption == "default") { + GlobalStates::Init(); + } else { + fs::path path(dataDirOption); + GlobalStates::Init(std::move(path)); + } + DEFER + { + GlobalStates::Shutdown(); + }; + + // Main loop + backend->RunUntilWindowClose(&UI::MainWindow); + + return 0; +} +#else +# define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +# include +#endif diff --git a/app/source/Cplt/Locale/zh_CN.h b/app/source/Cplt/Locale/zh_CN.h new file mode 100644 index 0000000..f654464 --- /dev/null +++ b/app/source/Cplt/Locale/zh_CN.h @@ -0,0 +1,159 @@ +#pragma once + +#define L10N_ERROR "错误" +#define L10N_ADD "新建" +#define L10N_EDIT "编辑" +#define L10N_DELETE "删除" +#define L10N_RENAME "重命名" +#define L10N_DISCONNECT "断开连接" +#define L10N_OPEN "打开" +#define L10N_CLOSE "关闭" +#define L10N_CONFIRM "确定" +#define L10N_CANCEL "取消" +#define L10N_NAME "名称" +#define L10N_TYPE "类型" + +#define L10N_INVALID_PATH_ERROR "无效路径" +#define L10N_EMPTY_NAME_ERROR "名称不能为空" +#define L10N_DUPLICATE_NAME_ERROR "名称已被占用" + +#define L10N_MAIN_TAB_SETTINGS "设置" +#define L10N_MAIN_WINDOW_TAB_PROJECT "项目" +#define L10N_MAIN_WINDOW_TAB_DATABASE_VIEW "数据" +#define L10N_MAIN_WINDOW_TAB_ITEMS "物品" +#define L10N_MAIN_WINDOW_TAB_WORKFLOWS "工作流" +#define L10N_MAIN_WINDOW_TAB_TEMPLATES "模板" + +#define L10N_PROJECT_NEW "新建项目..." +#define L10N_PROJECT_NEW_DIALOG_TITLE "新建项目向导" +#define L10N_PROJECT_NAME "项目名称" +#define L10N_PROJECT_PATH "项目路径" +#define L10N_PROJECT_NEW_PATH_DIALOG_TITLE "项目路径" +#define L10N_PROJECT_OPEN "打开项目..." +#define L10N_PROJECT_OPEN_DIALOG_TITLE "打开项目" +#define L10N_PROJECT_RECENTS "最近使用" +#define L10N_PROJECT_RECENTS_CLEAR "清空" +#define L10N_PROJECT_RECENTS_NONE_PRESENT "(暂无最近使用的项目)" +#define L10N_PROJECT_RECENTS_OPEN_TOOLTIP "打开该项目" +#define L10N_PROJECT_RECENTS_DELETE_TOOLTIP "将该项目从最近使用列表中删除,项目本身将不受影响。" +#define L10N_PROJECT_INVALID_PROJECT_FORMAT "无效的项目文件" + +#define L10N_PROJECT_OPEN_IN_FILESYSTEM "在文件系统中打开" + +#define L10N_DATABASE_SALES_VIEW_TAB_NAME "销售" +#define L10N_DATABASE_SALES_VIEW_EDIT_DIALOG_TITLE "编辑销售记录" +#define L10N_DATABASE_PURCHASES_VIEW_TAB_NAME "采购" +#define L10N_DATABASE_PURCHASES_VIEW_EDIT_DIALOG_TITLE "编辑采购记录" +#define L10N_DATABASE_COLUMN_ITEMS "项目" +#define L10N_DATABASE_COLUMN_CUSTOMER "客户" +#define L10N_DATABASE_COLUMN_FACTORY "工厂" +/// 销售订单的交货期限 +#define L10N_DATABASE_COLUMN_DEADLINE "交货期限" +/// 采购订单的下单时间 +#define L10N_DATABASE_COLUMN_ORDER_TIME "下单时间" +/// 所有订单的“完成”时间。对于销售来说是实际交货时间,对于采购来说是收货时间。 +#define L10N_DATABASE_COLUMN_DELIVERY_TIME "交货时间" +/// 运输批次的发货时间,适用于采购和销售批次。 +#define L10N_DATABASE_COLUMN_SHIPMENT_TIME "发货时间" +/// 运输批次的收获时间,适用于采购和销售批次。 +#define L10N_DATABASE_COLUMN_ARRIVAL_TIME "实际到达时间" +#define L10N_DATABASE_MESSAGE_NO_ORDER_SELECTED "选择任意一个订单以查看与其相关的批次" +#define L10N_DATABASE_MESSAGE_NOT_DELIVERED "N/A" + +#define L10N_ITEM_ADD_DIALOG_TITLE "新建物品项" +#define L10N_ITEM_EDIT_DIALOG_TITLE "编辑物品项" +#define L10N_ITEM_DELETE_DIALOG_TITLE "删除物品项" +#define L10N_ITEM_DELETE_DIALOG_MESSAGE "确定删除该物品项吗?" +#define L10N_ITEM_CATEGORY_PRODUCT "产品" +#define L10N_ITEM_CATEGORY_FACTORY "工厂" +#define L10N_ITEM_CATEGORY_CUSTOMER "客户" +#define L10N_ITEM_COLUMN_NAME "名称" +#define L10N_ITEM_COLUMN_DESCRIPTION "描述" +#define L10N_ITEM_COLUMN_EMAIL "邮箱" +#define L10N_ITEM_COLUMN_STOCK "库存" +#define L10N_ITEM_COLUMN_PRICE "价格" + +#define L10N_ASSET_OPEN "打开资源..." +#define L10N_ASSET_OPEN_DIALOG_TITLE "打开资源" +#define L10N_ASSET_MANAGE "管理资源..." +#define L10N_ASSET_MANAGE_DIALOG_TITLE "管理资源" +#define L10N_ADD_ASSET_DIALOG_TITLE "新建资源向导" +#define L10N_DELETE_ASSET_DIALOG_TITLE "确认删除资源" +#define L10N_RENAME_ASSET_DIALOG_TITLE "重命名资源" +#define L10N_TEMPLATE_INVALID_TYPE_ERROR "无效的模板类型" + +#define L10N_VALUE_NUMERIC "数值" +#define L10N_VALUE_TEXT "文本" +#define L10N_VALUE_DATE_TIME "时间戳" +#define L10N_VALUE_ROW_ID "数据库表格行" +#define L10N_VALUE_LIST "列表" +#define L10N_VALUE_DICT "字典" +#define L10N_VALUE_OBJECT "对象" +#define L10N_VALUE_SALE_RECORD "销售记录" +#define L10N_VALUE_PURCHASE_RECORD "采购记录" + +#define L10N_VALUE_PROPERTY_CUSTOMER "客户" +#define L10N_VALUE_PROPERTY_DEADLINE "交货期限" +#define L10N_VALUE_PROPERTY_FACTORY "工厂" +#define L10N_VALUE_PROPERTY_ORDER_TIME "下单时间" +#define L10N_VALUE_PROPERTY_DELIVERY_TIME "交货时间" + +#define L10N_WORKFLOW_KIND_INPUT "输入节点" +#define L10N_WORKFLOW_KIND_TRANSFORM "计算节点" +#define L10N_WORKFLOW_KIND_OUTPUT "输出节点" + +#define L10N_WORKFLOW_ADD "加法" +#define L10N_WORKFLOW_SUB "减法" +#define L10N_WORKFLOW_MUL "乘法" +#define L10N_WORKFLOW_DIV "除法" +#define L10N_WORKFLOW_EVAL "对表达式求值" +#define L10N_WORKFLOW_FMT "格式化文本" +#define L10N_WORKFLOW_INSTANTIATE_TEMPLATE "实例化文档" +#define L10N_WORKFLOW_FORM_INPUT "表单输入" +#define L10N_WORKFLOW_DB_INPUT "数据库输入" + +#define L10N_WORKFLOW_CATEGORY_NUMERIC "数字" +#define L10N_WORKFLOW_CATEGORY_TEXT "文本" +#define L10N_WORKFLOW_CATEGORY_DOCUMENT "文档" +#define L10N_WORKFLOW_CATEGORY_USER_INPUT "用户输入" +#define L10N_WORKFLOW_CATEGORY_SYS_INPUT "环境输入" +#define L10N_WORKFLOW_CATEGORY_OUTPUT "输出" + +#define L10N_WORKFLOW_RTERROR_DIV_BY_0 "错误:除数为0" + +#define L10N_TEMPLATE_TABLE "表格模板" + +#define L10N_TABLE "表格" +#define L10N_TABLE_CONFIGURE_PROPERTIES "编辑表格属性..." +#define L10N_TABLE_PROPERTIES "表格属性" +#define L10N_TABLE_EDIT_TABLE "编辑表格" +#define L10N_TABLE_EDIT_RESIZE_COLS "调整列宽度" +#define L10N_TABLE_EDIT_RESIZE_ROWS "调整行高度" +#define L10N_TABLE_WIDTH "宽度" +#define L10N_TABLE_HEIGHT "长度" +#define L10N_TABLE_SINGLE_PARAMS "参数" +#define L10N_TABLE_ARRAY_GROUPS "列表参数组" +#define L10N_TABLE_CELL "单元格" +#define L10N_TABLE_CELL_POS "位置:%s%s" +#define L10N_TABLE_CELL_TYPE_CONST "类型:普通单元格" +#define L10N_TABLE_CELL_TYPE_PARAM "类型:参数单元格" +#define L10N_TABLE_CELL_TYPE_CREATE_AG "类型:列表参数组" +#define L10N_TABLE_CELL_CONV_CONST "转换为普通单元格" +#define L10N_TABLE_CELL_CONV_PARAM "转换为参数单元格" +#define L10N_TABLE_CELL_CONV_CREATE_AG "创建列表参数组" +#define L10N_TABLE_CELL_CONV_ADD_AG_LEFT "添加至左侧的列表参数组" +#define L10N_TABLE_CELL_CONV_ADD_AG_RIGHT "添加至右侧的列表参数组" +#define L10N_TABLE_CELL_SELECT_MSG "请单击选择一个单元格以编辑其属性" +#define L10N_TABLE_CELL_HORIZONTAL_ALIGNMENT "水平对齐" +#define L10N_TABLE_CELL_CONTENT "内容" +#define L10N_TABLE_CELL_VAR_NAME "变量名" +#define L10N_TABLE_CELL_VAR_TOOLTIP "参数单元格的唯一名称(表格内不得重复)。" +#define L10N_TABLE_CELL_ARRAY_VAR_TOOLTIP "列表参数组内单个参数单元格的名称,在组内唯一(不同的参数组可以包含相同的参数名)。" +#define L10N_TABLE_CELL_VAR_NAME_DUP "参数名已重复" +#define L10N_TABLE_CELL_VERTICAL_ALIGNMENT "垂直对齐" +#define L10N_TABLE_CELL_ALIGN_LEFT "左对齐" +#define L10N_TABLE_CELL_ALIGN_CENTER "居中" +#define L10N_TABLE_CELL_ALIGN_RIGHT "右对齐" +#define L10N_TABLE_CELL_ALIGN_TOP "顶部对齐" +#define L10N_TABLE_CELL_ALIGN_MIDDLE "居中" +#define L10N_TABLE_CELL_ALIGN_BOTTOM "底部对齐" diff --git a/app/source/Cplt/Model/Assets.cpp b/app/source/Cplt/Model/Assets.cpp new file mode 100644 index 0000000..0dfe847 --- /dev/null +++ b/app/source/Cplt/Model/Assets.cpp @@ -0,0 +1,306 @@ +#include "Assets.hpp" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace std::literals::string_view_literals; +namespace fs = std::filesystem; + +template +void OperateStreamForSavedAsset(TSavedAsset& cell, TStream& proxy) +{ + proxy.template ObjectAdapted(cell.Name); + proxy.template ObjectAdapted(cell.Uuid); + proxy.Value(cell.Payload); +} + +void SavedAsset::ReadFromDataStream(InputDataStream& stream) +{ + ::OperateStreamForSavedAsset(*this, stream); +} + +void SavedAsset::WriteToDataStream(OutputDataStream& stream) const +{ + ::OperateStreamForSavedAsset(*this, stream); +} + +Asset::Asset() = default; + +class AssetList::Private +{ +public: + Project* ConnectedProject; + tsl::array_map Assets; + tsl::array_map> Cache; + int CacheSizeLimit = 0; + + struct + { + std::string NewName; + NameSelectionError NewNameError = NameSelectionError::Empty; + + void ShowErrors() const + { + switch (NewNameError) { + case NameSelectionError::None: break; + case NameSelectionError::Duplicated: + ImGui::ErrorMessage(I18N_TEXT("Duplicate name", L10N_DUPLICATE_NAME_ERROR)); + break; + case NameSelectionError::Empty: + ImGui::ErrorMessage(I18N_TEXT("Name cannot be empty", L10N_EMPTY_NAME_ERROR)); + break; + } + } + + bool HasErrors() const + { + return NewNameError != NameSelectionError::None; + } + + void Validate(const AssetList& self) + { + if (NewName.empty()) { + NewNameError = NameSelectionError::Empty; + return; + } + + if (self.FindByName(NewName)) { + NewNameError = NameSelectionError::Duplicated; + return; + } + + NewNameError = NameSelectionError::None; + } + } PopupPrivateState; +}; + +AssetList::AssetList(Project& project) + : mPrivate{ std::make_unique() } +{ + mPrivate->ConnectedProject = &project; +} + +// Write an empty destructor here so std::unique_ptr's destructor can see AssetList::Private's implementation +AssetList::~AssetList() +{ +} + +Project& AssetList::GetConnectedProject() const +{ + return *mPrivate->ConnectedProject; +} + +void AssetList::Reload() +{ + // TODO fix asset dicovery loading + mPrivate->Assets.clear(); + mPrivate->Cache.clear(); + DiscoverFiles([this](SavedAsset asset) -> void { + mPrivate->Assets.insert(asset.Name, std::move(asset)); + }); +} + +int AssetList::GetCount() const +{ + return mPrivate->Assets.size(); +} + +const tsl::array_map& AssetList::GetAssets() const +{ + return mPrivate->Assets; +} + +const SavedAsset* AssetList::FindByName(std::string_view name) const +{ + auto iter = mPrivate->Assets.find(name); + if (iter != mPrivate->Assets.end()) { + return &iter.value(); + } else { + return nullptr; + } +} + +const SavedAsset& AssetList::Create(SavedAsset asset) +{ + auto [iter, DISCARD] = mPrivate->Assets.insert(asset.Name, SavedAsset{}); + auto& savedAsset = iter.value(); + + savedAsset = std::move(asset); + if (savedAsset.Uuid.is_nil()) { + savedAsset.Uuid = uuids::uuid_random_generator{}(); + } + + SaveInstance(savedAsset, nullptr); + + return savedAsset; +} + +std::unique_ptr AssetList::CreateAndLoad(SavedAsset assetIn) +{ + auto& savedAsset = Create(std::move(assetIn)); + auto asset = std::unique_ptr(CreateInstance(savedAsset)); + return asset; +} + +std::unique_ptr AssetList::Load(std::string_view name) const +{ + if (auto savedAsset = FindByName(name)) { + auto asset = Load(*savedAsset); + return asset; + } else { + return nullptr; + } +} + +std::unique_ptr AssetList::Load(const SavedAsset& asset) const +{ + return std::unique_ptr(LoadInstance(asset)); +} + +const SavedAsset* AssetList::Rename(std::string_view oldName, std::string_view newName) +{ + auto iter = mPrivate->Assets.find(oldName); + if (iter == mPrivate->Assets.end()) return nullptr; + + auto info = std::move(iter.value()); + info.Name = newName; + + RenameInstanceOnDisk(info, oldName); + + mPrivate->Assets.erase(iter); + auto [newIter, DISCARD] = mPrivate->Assets.insert(newName, std::move(info)); + + return &newIter.value(); +} + +bool AssetList::Remove(std::string_view name) +{ + auto iter = mPrivate->Assets.find(name); + if (iter == mPrivate->Assets.end()) { + return false; + } + auto& asset = iter.value(); + + fs::remove(RetrievePathFromAsset(asset)); + mPrivate->Assets.erase(iter); + + return true; +} + +int AssetList::GetCacheSizeLimit() const +{ + return mPrivate->CacheSizeLimit; +} + +void AssetList::SetCacheSizeLimit(int limit) +{ + mPrivate->CacheSizeLimit = limit; +} + +void AssetList::DisplayIconsList(ListState& state) +{ + // TODO +} + +void AssetList::DisplayDetailsList(ListState& state) +{ + // Note: stub function remained here in case any state processing needs to be done before issuing to implementers + DisplayDetailsTable(state); +} + +void AssetList::DisplayControls(ListState& state) +{ + auto& ps = mPrivate->PopupPrivateState; + bool openedDummy = true; + + if (ImGui::Button(ICON_FA_PLUS " " I18N_TEXT("Add", L10N_ADD))) { + ImGui::OpenPopup(I18N_TEXT("Add asset wizard", L10N_ADD_ASSET_DIALOG_TITLE)); + } + if (ImGui::BeginPopupModal(I18N_TEXT("Add asset wizard", L10N_ADD_ASSET_DIALOG_TITLE), &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { + DisplayAssetCreator(state); + ImGui::EndPopup(); + } + + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_I_CURSOR " " I18N_TEXT("Rename", L10N_RENAME), state.SelectedAsset == nullptr)) { + ImGui::OpenPopup(I18N_TEXT("Rename asset wizard", L10N_RENAME_ASSET_DIALOG_TITLE)); + } + if (ImGui::BeginPopupModal(I18N_TEXT("Rename asset wizard", L10N_RENAME_ASSET_DIALOG_TITLE), &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { + if (ImGui::InputText(I18N_TEXT("Name", L10N_NAME), &ps.NewName)) { + ps.Validate(*this); + } + + if (ImGui::Button(I18N_TEXT("Confirm", L10N_CONFIRM), ps.HasErrors())) { + ImGui::CloseCurrentPopup(); + + auto movedAsset = Rename(state.SelectedAsset->Name, ps.NewName); + // Update the selected pointer to the new location (we mutated the map, the pointer may be invalid now) + state.SelectedAsset = movedAsset; + + ps = {}; + } + ImGui::SameLine(); + if (ImGui::Button(I18N_TEXT("Cancel", L10N_CANCEL))) { + ImGui::CloseCurrentPopup(); + } + + ps.ShowErrors(); + + ImGui::EndPopup(); + } + + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_TRASH " " I18N_TEXT("Delete", L10N_DELETE), state.SelectedAsset == nullptr)) { + ImGui::OpenPopup(I18N_TEXT("Delete asset", L10N_DELETE_ASSET_DIALOG_TITLE)); + } + if (ImGui::BeginPopupModal(I18N_TEXT("Delete asset", L10N_DELETE_ASSET_DIALOG_TITLE), &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { + if (ImGui::Button(I18N_TEXT("Confirm", L10N_CONFIRM))) { + ImGui::CloseCurrentPopup(); + + auto& assetName = state.SelectedAsset->Name; + Remove(assetName); + + state.SelectedAsset = nullptr; + } + ImGui::SameLine(); + if (ImGui::Button(I18N_TEXT("Cancel", L10N_CANCEL))) { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } +} + +void AssetList::DiscoverFilesByExtension(const std::function& callback, const fs::path& containerDir, std::string_view extension) const +{ + for (auto entry : fs::directory_iterator(containerDir)) { + if (!entry.is_regular_file()) continue; + + // If the caller provided an extension to match against, and it doesn't equal to current file extension, skip + if (!extension.empty() && + entry.path().extension() != extension) + { + continue; + } + + callback(SavedAsset{ + .Name = RetrieveNameFromFile(entry.path()), + .Uuid = RetrieveUuidFromFile(entry.path()), + // TODO load payload + }); + } +} + +void AssetList::DiscoverFilesByHeader(const std::function& callback, const fs::path& containerDir, const std::function& validater) const +{ + // TODO +} diff --git a/app/source/Cplt/Model/Assets.hpp b/app/source/Cplt/Model/Assets.hpp new file mode 100644 index 0000000..d2f8570 --- /dev/null +++ b/app/source/Cplt/Model/Assets.hpp @@ -0,0 +1,130 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include + +/// A structure representing a ready-to-be-loaded asset, locating on the disk. +/// Each asset should be identified by a unique uuid within the asset category (i.e. a workflow and a template can share the same uuid), +/// generated on insertion to an asset list if not given by the caller. +struct SavedAsset +{ + std::string Name; + /// UUID of this asset. This field is generated as a random UUID v4 upon insertion into an AssetList, if not already provided by the caller (indicated by !is_nil()). + uuids::uuid Uuid; + /// Extra data to be used by the AssetList/Asset implementation. + uint64_t Payload; + + void ReadFromDataStream(InputDataStream& stream); + void WriteToDataStream(OutputDataStream& stream) const; +}; + +class Asset +{ +public: + Asset(); + virtual ~Asset() = default; +}; + +enum class NameSelectionError +{ + None, + Duplicated, + Empty, +}; + +class AssetList +{ +private: + class Private; + std::unique_ptr mPrivate; + +public: + AssetList(Project& project); + virtual ~AssetList(); + + Project& GetConnectedProject() const; + + // TODO support file watches + void Reload(); + + int GetCount() const; + // TODO convert to custom iterable + const tsl::array_map& GetAssets() const; + + const SavedAsset* FindByName(std::string_view name) const; + const SavedAsset& Create(SavedAsset asset); + std::unique_ptr CreateAndLoad(SavedAsset asset); + /// Load the asset on disk by its name. + std::unique_ptr Load(std::string_view name) const; + /// Load the asset on disk by a reference to its SavedAsset instance. This function assumes that the given SavedAsset + /// is stored in AssetList, otherwise the behavior is undefined. + std::unique_ptr Load(const SavedAsset& asset) const; + const SavedAsset* Rename(std::string_view oldName, std::string_view newName); + bool Remove(std::string_view name); + + int GetCacheSizeLimit() const; + void SetCacheSizeLimit(int limit); + + struct ListState + { + const SavedAsset* SelectedAsset = nullptr; + }; + void DisplayIconsList(ListState& state); + void DisplayDetailsList(ListState& state); + void DisplayControls(ListState& state); + +protected: + virtual void DiscoverFiles(const std::function& callback) const = 0; + + // Helper + void DiscoverFilesByExtension(const std::function& callback, const std::filesystem::path& containerDir, std::string_view extension) const; + void DiscoverFilesByHeader(const std::function& callback, const std::filesystem::path& containerDir, const std::function& validater) const; + + /// Create an empty/default instance of this asset type on disk, potentially qualified by SavedAsset::Payload. + /// Return `true` on success and `false` on failure. + virtual bool SaveInstance(const SavedAsset& assetInfo, const Asset* asset) const = 0; + /// The returned pointer indicate ownership to the object. + virtual Asset* LoadInstance(const SavedAsset& assetInfo) const = 0; + /// Create an empty/default instance of this asset type, potentially qualified by SavedAsset::Payload. + /// The returned pointer indicate ownership to the object. + virtual Asset* CreateInstance(const SavedAsset& assetInfo) const = 0; + virtual bool RenameInstanceOnDisk(const SavedAsset& assetInfo, std::string_view oldName) const = 0; + + virtual std::string RetrieveNameFromFile(const std::filesystem::path& file) const = 0; + virtual uuids::uuid RetrieveUuidFromFile(const std::filesystem::path& file) const = 0; + virtual std::filesystem::path RetrievePathFromAsset(const SavedAsset& asset) const = 0; + + virtual void DisplayAssetCreator(ListState& state) = 0; + virtual void DisplayDetailsTable(ListState& state) const = 0; +}; + +template +class AssetListTyped : public AssetList +{ +public: + using AssetList::AssetList; + +#pragma clang diagnostic push +#pragma ide diagnostic ignored "HidingNonVirtualFunction" + std::unique_ptr CreateAndLoad(SavedAsset asset) + { + return std::unique_ptr(static_cast(AssetList::CreateAndLoad(asset).release())); + } + + std::unique_ptr Load(std::string_view name) const + { + return std::unique_ptr(static_cast(AssetList::Load(name).release())); + } + + std::unique_ptr Load(const SavedAsset& asset) const + { + return std::unique_ptr(static_cast(AssetList::Load(asset).release())); + } +#pragma clang diagnostic pop +}; diff --git a/app/source/Cplt/Model/Database.cpp b/app/source/Cplt/Model/Database.cpp new file mode 100644 index 0000000..07c6e36 --- /dev/null +++ b/app/source/Cplt/Model/Database.cpp @@ -0,0 +1,163 @@ +#include "Database.hpp" + +#include + +#include +#include + +namespace fs = std::filesystem; + +SalesTable::SalesTable(MainDatabase& db) + // language=SQLite + : GetRowCount(db.GetSQLite(), "SELECT Count(*) FROM Sales") + // language=SQLite + , GetRows(db.GetSQLite(), "SELECT * FROM Sales LIMIT ? OFFSET ?") + // language=SQLite + , GetItems(db.GetSQLite(), "SELECT * FROM SalesItems WHERE SaleId == ?") +{ +} + +PurchasesTable::PurchasesTable(MainDatabase& db) + // language=SQLite + : GetRowCount(db.GetSQLite(), "SELECT Count(*) FROM Purchases") + // language=SQLite + , GetRows(db.GetSQLite(), "SELECT * FROM Purchases LIMIT ? OFFSET ?") + // language=SQLite + , GetItems(db.GetSQLite(), "SELECT * FROM PurchasesItems WHERE PurchaseId == ?") +{ +} + +DeliveryTable::DeliveryTable(MainDatabase& db) + // language=SQLite + : FilterByTypeAndId(db.GetSQLite(), "SELECT * FROM Deliveries WHERE AssociatedOrder == ? AND Outgoing = ?") + // language=SQLite + , GetItems(db.GetSQLite(), "SELECT * FROM DeliveriesItems WHERE DeliveryId == ?") +{ +} + +static std::string GetDatabaseFilePath(const Project& project) +{ + auto dbsDir = project.GetPath() / "databases"; + fs::create_directories(dbsDir); + + auto dbFile = dbsDir / "transactions.sqlite3"; + return dbFile.string(); +} + +/// Wrapper for SQLite::Database that creates the default tables +MainDatabase::DatabaseWrapper::DatabaseWrapper(MainDatabase& self) + : mSqlite(GetDatabaseFilePath(*self.mProject), SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE) +{ + // If this table doesn't exist, the database probably just got initialized + if (mSqlite.tableExists("Sales")) { + return; + } + + // 'Sales' schema + // - Customer: the customer item ID + // - Deadline: unix epoch time of order deadline + // - DeliveryTime: the time this order was completed (through a set of deliveries) + + // 'Purchases' schema + // - Factory: the factory id, + // - OrderTime: the time this order was made + // - DeliveryTime: the time this order was completed (through a set of deliveries) + + // 'Deliveries' schema + // - ShipmentTime: unix epoch time stamp of sending to delivery + // - ArrivalTime: unix epoch time stamp of delivery arrived at warehouse; 0 if not arrived yet + // - AssociatedOrder: Id of the order that this delivery is completing (which table: Outgoing=true -> Sales, Outgoing=false -> Purchases) + // - Outgoing: true if the delivery is from warehouse to customer; false if the delivery is from factory to warehouse + + // Note: the 'Id' key would be unique (not recycled after row deletion) because it's explicit + // https://www.sqlite.org/rowidtable.html + + // language=SQLite + mSqlite.exec(R"""( +CREATE TABLE IF NOT EXISTS Sales( + Id INT PRIMARY KEY, + Customer INT, + Deadline DATETIME, + DeliveryTime DATETIME +); +CREATE TABLE IF NOT EXISTS SalesItems( + SaleId INT, + ItemId INT, + Count INT +); + +CREATE TABLE IF NOT EXISTS Purchases( + Id INT PRIMARY KEY, + Factory INT, + OrderTime DATETIME, + DeliveryTime DATETIME +); +CREATE TABLE IF NOT EXISTS PurchasesItems( + PurchaseId INT, + ItemId INT, + Count INT +); + +CREATE TABLE IF NOT EXISTS Deliveries( + Id INT PRIMARY KEY, + ShipmentTime DATETIME, + ArrivalTime DATETIME, + AssociatedOrder INT, + Outgoing BOOLEAN +); +CREATE TABLE IF NOT EXISTS DeliveriesItems( + DeliveryId INT, + ItemId INT, + Count INT +); +)"""); +} + +MainDatabase::MainDatabase(Project& project) + : mProject{ &project } + , mDbWrapper(*this) + , mSales(*this) + , mPurchases(*this) + , mDeliveries(*this) +{ +} + +const SQLite::Database& MainDatabase::GetSQLite() const +{ + return mDbWrapper.mSqlite; +} + +SQLite::Database& MainDatabase::GetSQLite() +{ + return mDbWrapper.mSqlite; +} + +const SalesTable& MainDatabase::GetSales() const +{ + return mSales; +} + +SalesTable& MainDatabase::GetSales() +{ + return mSales; +} + +const PurchasesTable& MainDatabase::GetPurchases() const +{ + return mPurchases; +} + +PurchasesTable& MainDatabase::GetPurchases() +{ + return mPurchases; +} + +const DeliveryTable& MainDatabase::GetDeliveries() const +{ + return mDeliveries; +} + +DeliveryTable& MainDatabase::GetDeliveries() +{ + return mDeliveries; +} diff --git a/app/source/Cplt/Model/Database.hpp b/app/source/Cplt/Model/Database.hpp new file mode 100644 index 0000000..222e43d --- /dev/null +++ b/app/source/Cplt/Model/Database.hpp @@ -0,0 +1,79 @@ +#pragma once + +#include + +#include +#include +#include + +enum class TableKind +{ + Sales, + SalesItems, + Purchases, + PurchasesItems, + Deliveries, + DeliveriesItems, +}; + +class SalesTable +{ +public: + SQLite::Statement GetRowCount; + SQLite::Statement GetRows; + SQLite::Statement GetItems; + +public: + SalesTable(MainDatabase& db); +}; + +class PurchasesTable +{ +public: + SQLite::Statement GetRowCount; + SQLite::Statement GetRows; + SQLite::Statement GetItems; + +public: + PurchasesTable(MainDatabase& db); +}; + +class DeliveryTable +{ +public: + SQLite::Statement FilterByTypeAndId; + SQLite::Statement GetItems; + +public: + DeliveryTable(MainDatabase& db); +}; + +class MainDatabase +{ +private: + class DatabaseWrapper + { + public: + SQLite::Database mSqlite; + DatabaseWrapper(MainDatabase& self); + }; + + Project* mProject; + DatabaseWrapper mDbWrapper; + SalesTable mSales; + PurchasesTable mPurchases; + DeliveryTable mDeliveries; + +public: + MainDatabase(Project& project); + + const SQLite::Database& GetSQLite() const; + SQLite::Database& GetSQLite(); + + const SalesTable& GetSales() const; + SalesTable& GetSales(); + const PurchasesTable& GetPurchases() const; + PurchasesTable& GetPurchases(); + const DeliveryTable& GetDeliveries() const; + DeliveryTable& GetDeliveries(); +}; diff --git a/app/source/Cplt/Model/Filter.cpp b/app/source/Cplt/Model/Filter.cpp new file mode 100644 index 0000000..1e4b31b --- /dev/null +++ b/app/source/Cplt/Model/Filter.cpp @@ -0,0 +1 @@ +#include "Filter.hpp" diff --git a/app/source/Cplt/Model/Filter.hpp b/app/source/Cplt/Model/Filter.hpp new file mode 100644 index 0000000..1b923e1 --- /dev/null +++ b/app/source/Cplt/Model/Filter.hpp @@ -0,0 +1,6 @@ +#pragma once + +class TableRowsFilter +{ + // TODO +}; diff --git a/app/source/Cplt/Model/GlobalStates.cpp b/app/source/Cplt/Model/GlobalStates.cpp new file mode 100644 index 0000000..417514f --- /dev/null +++ b/app/source/Cplt/Model/GlobalStates.cpp @@ -0,0 +1,163 @@ +#include "GlobalStates.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +static std::unique_ptr globalStateInstance; +static fs::path globalDataPath; + +void GlobalStates::Init() +{ + Init(StandardDirectories::UserData() / "cplt"); +} + +void GlobalStates::Init(std::filesystem::path userDataDir) +{ + globalStateInstance = std::make_unique(); + globalDataPath = userDataDir; + fs::create_directories(globalDataPath); + + // Reading recent projects + { + std::ifstream ifs(globalDataPath / "recents.json"); + if (!ifs) return; + + Json::Value root; + ifs >> root; + + if (!root.isObject()) return; + if (auto& recents = root["RecentProjects"]; recents.isArray()) { + for (auto& elm : recents) { + if (!elm.isString()) continue; + + fs::path path(elm.asCString()); + if (!fs::exists(path)) continue; + + auto utf8String = path.string(); + globalStateInstance->mRecentProjects.push_back(RecentProject{ + .Path = std::move(path), + .CachedUtf8String = std::move(utf8String), + }); + } + } + } +} + +void GlobalStates::Shutdown() +{ + if (!globalStateInstance) return; + + globalStateInstance->SetCurrentProject(nullptr); + + if (globalStateInstance->mDirty) { + globalStateInstance->WriteToDisk(); + } +} + +GlobalStates& GlobalStates::GetInstance() +{ + return *globalStateInstance; +} + +const std::filesystem::path& GlobalStates::GetUserDataPath() +{ + return globalDataPath; +} + +const std::vector& GlobalStates::GetRecentProjects() const +{ + return mRecentProjects; +} + +void GlobalStates::ClearRecentProjects() +{ + mRecentProjects.clear(); + MarkDirty(); +} + +void GlobalStates::AddRecentProject(const Project& project) +{ + mRecentProjects.push_back(RecentProject{ + .Path = project.GetPath(), + .CachedUtf8String = project.GetPath().string(), + }); + MarkDirty(); +} + +void GlobalStates::MoveProjectToTop(const Project& project) +{ + for (auto it = mRecentProjects.begin(); it != mRecentProjects.end(); ++it) { + if (it->Path == project.GetPath()) { + std::rotate(it, it + 1, mRecentProjects.end()); + MarkDirty(); + return; + } + } + AddRecentProject(project); +} + +void GlobalStates::RemoveRecentProject(int idx) +{ + assert(idx >= 0 && idx < mRecentProjects.size()); + + mRecentProjects.erase(mRecentProjects.begin() + idx); + MarkDirty(); +} + +bool GlobalStates::HasCurrentProject() const +{ + return mCurrentProject != nullptr; +} + +Project* GlobalStates::GetCurrentProject() const +{ + return mCurrentProject.get(); +} + +void GlobalStates::SetCurrentProject(std::unique_ptr project) +{ + if (mCurrentProject) { + mCurrentProject->WriteToDisk(); + mCurrentProject = nullptr; + } + if (project) { + MoveProjectToTop(*project); + } + mCurrentProject = std::move(project); +} + +void GlobalStates::WriteToDisk() const +{ + Json::Value root; + + auto& recentProjects = root["RecentProjects"] = Json::Value(Json::arrayValue); + for (auto& [path, _] : mRecentProjects) { + recentProjects.append(Json::Value(path.string())); + } + + std::ofstream ofs(globalDataPath / "recents.json"); + ofs << root; + + mDirty = false; +} + +bool GlobalStates::IsDirty() const +{ + return mDirty; +} + +void GlobalStates::MarkDirty() +{ + mDirty = true; + OnModified(); +} diff --git a/app/source/Cplt/Model/GlobalStates.hpp b/app/source/Cplt/Model/GlobalStates.hpp new file mode 100644 index 0000000..1eb47fb --- /dev/null +++ b/app/source/Cplt/Model/GlobalStates.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include +#include + +#include +#include +#include + +class GlobalStates +{ +public: + static void Init(); + static void Init(std::filesystem::path userDataDir); + static void Shutdown(); + + static GlobalStates& GetInstance(); + static const std::filesystem::path& GetUserDataPath(); + + struct RecentProject + { + std::filesystem::path Path; + std::string CachedUtf8String; + }; + +public: + Signal<> OnModified; + +private: + std::vector mRecentProjects; + std::unique_ptr mCurrentProject; + mutable bool mDirty = false; + +public: + const std::vector& GetRecentProjects() const; + void ClearRecentProjects(); + void AddRecentProject(const Project& project); + /// Move or add the project to end of the recent projects list. + /// If the project is not in the list of recently used projects, it will be appended, otherwise + /// it will be moved to the end. + void MoveProjectToTop(const Project& project); + void RemoveRecentProject(int idx); + + bool HasCurrentProject() const; + Project* GetCurrentProject() const; + void SetCurrentProject(std::unique_ptr project); + + // TODO async autosaving to prevent data loss on crash + void WriteToDisk() const; + + bool IsDirty() const; + +private: + void MarkDirty(); +}; diff --git a/app/source/Cplt/Model/Items.cpp b/app/source/Cplt/Model/Items.cpp new file mode 100644 index 0000000..9d2abc6 --- /dev/null +++ b/app/source/Cplt/Model/Items.cpp @@ -0,0 +1,114 @@ +#include "Items.hpp" + +const std::string& ProductItem::GetDescription() const +{ + return mDescription; +} + +void ProductItem::SetDescription(std::string description) +{ + mDescription = std::move(description); +} + +int ProductItem::GetPrice() const +{ + return mPrice; +} +void ProductItem::SetPrice(int price) +{ + mPrice = price; +} + +int ProductItem::GetStock() const +{ + return mStock; +} + +void ProductItem::SetStock(int stock) +{ + mStock = stock; +} + +Json::Value ProductItem::Serialize() const +{ + Json::Value elm; + elm["Description"] = mDescription; + elm["Price"] = mPrice; + elm["Stock"] = mStock; + return elm; +} + +void ProductItem::Deserialize(const Json::Value& elm) +{ + mDescription = elm["Description"].asString(); + mPrice = elm["Price"].asInt(); + mStock = elm["Stock"].asInt(); +} + +const std::string& FactoryItem::GetDescription() const +{ + return mDescription; +} + +void FactoryItem::SetDescription(std::string description) +{ + mDescription = std::move(description); +} + +const std::string& FactoryItem::GetEmail() const +{ + return mEmail; +} + +void FactoryItem::SetEmail(std::string email) +{ + mEmail = std::move(email); +} + +Json::Value FactoryItem::Serialize() const +{ + Json::Value elm; + elm["Description"] = mDescription; + elm["Email"] = mEmail; + return elm; +} + +void FactoryItem::Deserialize(const Json::Value& elm) +{ + mDescription = elm["Description"].asString(); + mEmail = elm["Email"].asString(); +} + +const std::string& CustomerItem::GetDescription() const +{ + return mDescription; +} + +void CustomerItem::SetDescription(std::string description) +{ + mDescription = std::move(description); +} + +const std::string& CustomerItem::GetEmail() const +{ + return mEmail; +} + +void CustomerItem::SetEmail(std::string email) +{ + mEmail = std::move(email); +} + +Json::Value CustomerItem::Serialize() const +{ + Json::Value elm; + elm["Description"] = mDescription; + elm["Email"] = mEmail; + return elm; +} + +void CustomerItem::Deserialize(const Json::Value& elm) +{ + mDescription = elm["Description"].asString(); + mEmail = elm["Email"].asString(); +} diff --git a/app/source/Cplt/Model/Items.hpp b/app/source/Cplt/Model/Items.hpp new file mode 100644 index 0000000..c00ee59 --- /dev/null +++ b/app/source/Cplt/Model/Items.hpp @@ -0,0 +1,253 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +template +class ItemList +{ +private: + std::vector mStorage; + tsl::array_map mNameLookup; + +public: + template + T& Insert(std::string name, Args... args) + { + auto iter = mNameLookup.find(name); + if (iter != mNameLookup.end()) { + throw std::runtime_error("Duplicate key."); + } + + for (size_t i = 0; i < mStorage.size(); ++i) { + if (mStorage[i].IsInvalid()) { + mStorage[i] = T(*this, i, std::move(name), std::forward(args)...); + mNameLookup.insert(name, i); + return mStorage[i]; + } + } + + size_t id = mStorage.size(); + mNameLookup.insert(name, id); + mStorage.emplace_back(*this, id, std::move(name), std::forward(args)...); + return mStorage[id]; + } + + void Remove(size_t index) + { + auto& item = mStorage[index]; + mNameLookup.erase(item.GetName()); + mStorage[index] = T(*this); + } + + T* Find(size_t id) + { + return &mStorage[id]; + } + + const T* Find(size_t id) const + { + return &mStorage[id]; + } + + const T* Find(std::string_view name) const + { + auto iter = mNameLookup.find(name); + if (iter != mNameLookup.end()) { + return &mStorage[iter.value()]; + } else { + return nullptr; + } + } + + Json::Value Serialize() const + { + Json::Value items(Json::arrayValue); + for (auto& item : mStorage) { + if (!item.IsInvalid()) { + auto elm = item.Serialize(); + elm["Id"] = item.GetId(); + elm["Name"] = item.GetName(); + items.append(elm); + } + } + + Json::Value root; + root["MaxItemId"] = mStorage.size(); + root["Items"] = std::move(items); + + return root; + } + + ItemList() = default; + + ItemList(const Json::Value& root) + { + constexpr const char* kMessage = "Failed to load item list from JSON."; + + auto& itemCount = root["MaxItemId"]; + if (!itemCount.isIntegral()) throw std::runtime_error(kMessage); + + mStorage.resize(itemCount.asInt64(), T(*this)); + + auto& items = root["Items"]; + if (!items.isArray()) throw std::runtime_error(kMessage); + + for (auto& elm : items) { + if (!elm.isObject()) throw std::runtime_error(kMessage); + + auto& id = elm["Id"]; + if (!id.isIntegral()) throw std::runtime_error(kMessage); + auto& name = elm["Name"]; + if (!name.isString()) throw std::runtime_error(kMessage); + + size_t iid = id.asInt64(); + mStorage[iid] = T(*this, iid, name.asString()); + mStorage[iid].Deserialize(elm); + } + } + + typename decltype(mStorage)::iterator begin() + { + return mStorage.begin(); + } + + typename decltype(mStorage)::iterator end() + { + return mStorage.end(); + } + + typename decltype(mStorage)::const_iterator begin() const + { + return mStorage.begin(); + } + + typename decltype(mStorage)::const_iterator end() const + { + return mStorage.end(); + } + +private: + template + friend class ItemBase; + + void UpdateItemName(const T& item, const std::string& newName) + { + mNameLookup.erase(item.GetName()); + mNameLookup.insert(newName, item.GetId()); + } +}; + +template +class ItemBase +{ +private: + ItemList* mList; + size_t mId; + std::string mName; + +public: + ItemBase(ItemList& list, size_t id = std::numeric_limits::max(), std::string name = "") + : mList{ &list } + , mId{ id } + , mName{ std::move(name) } + { + } + + bool IsInvalid() const + { + return mId == std::numeric_limits::max(); + } + + ItemList& GetList() const + { + return *mList; + } + + size_t GetId() const + { + return mId; + } + + const std::string& GetName() const + { + return mName; + } + + void SetName(std::string name) + { + mList->UpdateItemName(static_cast(*this), name); + mName = std::move(name); + } +}; + +class ProductItem : public ItemBase +{ +private: + std::string mDescription; + int mPrice = 0; + int mStock = 0; + +public: + using ItemBase::ItemBase; + + const std::string& GetDescription() const; + void SetDescription(std::string description); + /// Get the price of this item in US cents. + int GetPrice() const; + void SetPrice(int price); + /// Get the current number of this product in warehouse. + /// This is a housekeeping field and shouldn't be editable by the user from the UI. + int GetStock() const; + void SetStock(int stock); + + Json::Value Serialize() const; + void Deserialize(const Json::Value& elm); +}; + +class FactoryItem : public ItemBase +{ +private: + std::string mDescription; + std::string mEmail; + +public: + using ItemBase::ItemBase; + + const std::string& GetDescription() const; + void SetDescription(std::string description); + const std::string& GetEmail() const; + void SetEmail(std::string email); + + Json::Value Serialize() const; + void Deserialize(const Json::Value& elm); +}; + +class CustomerItem : public ItemBase +{ +private: + std::string mDescription; + std::string mEmail; + +public: + using ItemBase::ItemBase; + + const std::string& GetDescription() const; + void SetDescription(std::string description); + const std::string& GetEmail() const; + void SetEmail(std::string email); + + Json::Value Serialize() const; + void Deserialize(const Json::Value& elm); +}; diff --git a/app/source/Cplt/Model/Project.cpp b/app/source/Cplt/Model/Project.cpp new file mode 100644 index 0000000..a1e9bab --- /dev/null +++ b/app/source/Cplt/Model/Project.cpp @@ -0,0 +1,168 @@ +#include "Project.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +template +static void ReadItemList(ItemList& list, const fs::path& filePath) +{ + std::ifstream ifs(filePath); + if (ifs) { + Json::Value root; + ifs >> root; + + list = ItemList(root); + } +} + +static void CreateProjectSubfolders(const Project& project) +{ + fs::create_directory(project.GetDatabasesDirectory()); + fs::create_directory(project.GetItemsDirectory()); + fs::create_directory(project.GetWorkflowsDirectory()); + fs::create_directory(project.GetTemplatesDirectory()); +} + +Project::Project(fs::path rootPath) + : mRootPath{ std::move(rootPath) } + , mRootPathString{ mRootPath.string() } + , Workflows(*this) + , Templates(*this) + , Database(*this) +{ + // TODO better diagnostic + const char* kInvalidFormatErr = "Failed to load project: invalid format."; + + std::ifstream ifs(mRootPath / "cplt_project.json"); + if (!ifs) { + std::string message; + message += "Failed to load project file at '"; + message += mRootPath.string(); + message += "'."; + throw std::runtime_error(message); + } + + { + Json::Value root; + ifs >> root; + + const auto& croot = root; // Use const reference so that accessors default to returning a null if not found, instead of silently creating new elements + if (!croot.isObject()) { + throw std::runtime_error(kInvalidFormatErr); + } + + if (auto& name = croot["Name"]; name.isString()) { + mName = name.asString(); + } else { + throw std::runtime_error(kInvalidFormatErr); + } + } + + CreateProjectSubfolders(*this); + + auto itemsDir = mRootPath / "items"; + ReadItemList(Products, itemsDir / "products.json"); + ReadItemList(Factories, itemsDir / "factories.json"); + ReadItemList(Customers, itemsDir / "customers.json"); + + Workflows.Reload(); + Templates.Reload(); +} + +Project::Project(fs::path rootPath, std::string name) + : mRootPath{ std::move(rootPath) } + , mRootPathString{ mRootPath.string() } + , mName{ std::move(name) } + , Workflows(*this) + , Templates(*this) + , Database(*this) +{ + CreateProjectSubfolders(*this); +} + +const fs::path& Project::GetPath() const +{ + return mRootPath; +} + +const std::string& Project::GetPathString() const +{ + return mRootPathString; +} + +fs::path Project::GetDatabasesDirectory() const +{ + return mRootPath / "databases"; +} + +fs::path Project::GetItemsDirectory() const +{ + return mRootPath / "items"; +} + +fs::path Project::GetWorkflowsDirectory() const +{ + return mRootPath / "workflows"; +} + +fs::path Project::GetWorkflowPath(std::string_view name) const +{ + return (mRootPath / "workflows" / name).concat(".cplt-workflow"); +} + +fs::path Project::GetTemplatesDirectory() const +{ + return mRootPath / "templates"; +} + +fs::path Project::GetTemplatePath(std::string_view name) const +{ + return (mRootPath / "templates" / name).concat(".cplt-template"); +} + +const std::string& Project::GetName() const +{ + return mName; +} + +void Project::SetName(std::string name) +{ + mName = std::move(name); +} + +Json::Value Project::Serialize() +{ + Json::Value root(Json::objectValue); + + root["Name"] = mName; + + return root; +} + +template +static void WriteItemList(ItemList& list, const fs::path& filePath) +{ + std::ofstream ofs(filePath); + ofs << list.Serialize(); +} + +void Project::WriteToDisk() +{ + std::ofstream ofs(mRootPath / "cplt_project.json"); + ofs << this->Serialize(); + + auto itemsDir = GetItemsDirectory(); + WriteItemList(Products, itemsDir / "products.json"); + WriteItemList(Factories, itemsDir / "factories.json"); + WriteItemList(Customers, itemsDir / "customers.json"); +} diff --git a/app/source/Cplt/Model/Project.hpp b/app/source/Cplt/Model/Project.hpp new file mode 100644 index 0000000..8119a97 --- /dev/null +++ b/app/source/Cplt/Model/Project.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +class Project +{ +private: + std::filesystem::path mRootPath; + std::string mRootPathString; + std::string mName; + + // (Exception to style guidelines) + // This is put after the private fields, so that when XxxDatabase's constructor runs, all of them will be initialized +public: + WorkflowAssetList Workflows; + TemplateAssetList Templates; + ItemList Products; + ItemList Factories; + ItemList Customers; + MainDatabase Database; + +public: + /// Load the project from a directory containing the cplt_project.json file. + /// This only loads the main project file, the caller needs to + Project(std::filesystem::path rootPath); + + /// Create a project with the given name in the given path. Note that the path should be a directory that will contain the project files once created. + /// This function assumes the given directory will exist and is empty. + Project(std::filesystem::path rootPath, std::string name); + + /// Path to a *directory* that contains the project file. + const std::filesystem::path& GetPath() const; + const std::string& GetPathString() const; + + std::filesystem::path GetDatabasesDirectory() const; + std::filesystem::path GetItemsDirectory() const; + std::filesystem::path GetWorkflowsDirectory() const; + std::filesystem::path GetWorkflowPath(std::string_view name) const; + std::filesystem::path GetTemplatesDirectory() const; + std::filesystem::path GetTemplatePath(std::string_view name) const; + + const std::string& GetName() const; + void SetName(std::string name); + + Json::Value Serialize(); + void WriteToDisk(); +}; diff --git a/app/source/Cplt/Model/Template/TableTemplate.cpp b/app/source/Cplt/Model/Template/TableTemplate.cpp new file mode 100644 index 0000000..5cd9ed8 --- /dev/null +++ b/app/source/Cplt/Model/Template/TableTemplate.cpp @@ -0,0 +1,591 @@ +#include "TableTemplate.hpp" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +bool TableCell::IsDataHoldingCell() const +{ + return IsPrimaryCell() || !IsMergedCell(); +} + +bool TableCell::IsPrimaryCell() const +{ + return PrimaryCellLocation == Location; +} + +bool TableCell::IsMergedCell() const +{ + return PrimaryCellLocation.x == -1 || PrimaryCellLocation.y == -1; +} + +template +void OperateStreamForTableCell(TTableCell& cell, TStream& proxy) +{ + proxy.template ObjectAdapted(cell.Content); + proxy.Object(cell.Location); + proxy.Object(cell.PrimaryCellLocation); + proxy.Value(cell.SpanX); + proxy.Value(cell.SpanY); + proxy.Enum(cell.HorizontalAlignment); + proxy.Enum(cell.VerticalAlignment); + proxy.Enum(cell.Type); + proxy.Value(cell.DataId); +} + +void TableCell::ReadFromDataStream(InputDataStream& stream) +{ + ::OperateStreamForTableCell(*this, stream); +} + +void TableCell::WriteToDataStream(OutputDataStream& stream) const +{ + ::OperateStreamForTableCell(*this, stream); +} + +Vec2i TableArrayGroup::GetLeftCell() const +{ + return { Row, LeftCell }; +} + +Vec2i TableArrayGroup::GetRightCell() const +{ + return { Row, RightCell }; +} + +int TableArrayGroup::GetCount() const +{ + return RightCell - LeftCell + 1; +} + +Vec2i TableArrayGroup::FindCell(std::string_view name) +{ + // TODO + return Vec2i{}; +} + +template +static bool UpdateElementName(TMap& map, std::string_view oldName, std::string_view newName) +{ + auto iter = map.find(oldName); + if (iter == map.end()) { + return false; + } + + auto elm = iter.value(); + auto [DISCARD, inserted] = map.insert(newName, elm); + if (!inserted) { + return false; + } + + map.erase(iter); + return true; +} + +bool TableArrayGroup::UpdateCellName(std::string_view oldName, std::string_view newName) +{ + return ::UpdateElementName(mName2Cell, oldName, newName); +} + +template +void OperateStreamForTableArrayGroup(TTableArrayGroup& group, TStream& stream) +{ + stream.Value(group.Row); + stream.Value(group.LeftCell); + stream.Value(group.RightCell); +} + +void TableArrayGroup::ReadFromDataStream(InputDataStream& stream) +{ + ::OperateStreamForTableArrayGroup(*this, stream); +} + +void TableArrayGroup::WriteToDataStream(OutputDataStream& stream) const +{ + ::OperateStreamForTableArrayGroup(*this, stream); +} + +TableInstantiationParameters::TableInstantiationParameters(const TableTemplate& table) + : mTable{ &table } +{ +} + +TableInstantiationParameters& TableInstantiationParameters::ResetTable(const TableTemplate& newTable) +{ + mTable = &newTable; + return *this; +} + +TableInstantiationParameters TableInstantiationParameters::RebindTable(const TableTemplate& newTable) const +{ + TableInstantiationParameters result(newTable); + result.SingularCells = this->SingularCells; + result.ArrayGroups = this->ArrayGroups; + return result; +} + +const TableTemplate& TableInstantiationParameters::GetTable() const +{ + return *mTable; +} + +bool TableTemplate::IsInstance(const Template* tmpl) +{ + return tmpl->GetKind() == KD_Table; +} + +TableTemplate::TableTemplate() + : Template(KD_Table) +{ +} + +int TableTemplate::GetTableWidth() const +{ + return mColumnWidths.size(); +} + +int TableTemplate::GetTableHeight() const +{ + return mRowHeights.size(); +} + +void TableTemplate::Resize(int newWidth, int newHeight) +{ + // TODO this doesn't gracefully handle resizing to a smaller size which trims some merged cells + + std::vector cells; + cells.reserve(newWidth * newHeight); + + int tableWidth = GetTableWidth(); + int tableHeight = GetTableHeight(); + + for (int y = 0; y < newHeight; ++y) { + if (y >= tableHeight) { + for (int x = 0; x < newWidth; ++x) { + cells.push_back(TableCell{}); + } + continue; + } + + for (int x = 0; x < newWidth; ++x) { + if (x >= tableWidth) { + cells.push_back(TableCell{}); + } else { + auto& cell = GetCell({ x, y }); + cells.push_back(std::move(cell)); + } + } + } + + mCells = std::move(cells); + mColumnWidths.resize(newWidth, 80); + mRowHeights.resize(newHeight, 20); +} + +int TableTemplate::GetRowHeight(int row) const +{ + return mRowHeights[row]; +} + +void TableTemplate::SetRowHeight(int row, int height) +{ + mRowHeights[row] = height; +} + +int TableTemplate::GetColumnWidth(int column) const +{ + return mColumnWidths[column]; +} + +void TableTemplate::SetColumnWidth(int column, int width) +{ + mColumnWidths[column] = width; +} + +const TableCell& TableTemplate::GetCell(Vec2i pos) const +{ + int tableWidth = GetTableWidth(); + return mCells[pos.y * tableWidth + pos.x]; +} + +TableCell& TableTemplate::GetCell(Vec2i pos) +{ + return const_cast(const_cast(this)->GetCell(pos)); +} + +void TableTemplate::SetCellType(Vec2i pos, TableCell::CellType type) +{ + auto& cell = GetCell(pos); + if (cell.Type == type) { + return; + } + + switch (cell.Type) { + // Nothing to change + case TableCell::ConstantCell: break; + + case TableCell::SingularParametricCell: + mName2Parameters.erase(cell.Content); + break; + + case TableCell::ArrayParametricCell: { + auto& ag = mArrayGroups[cell.DataId]; + if (pos.x == ag.LeftCell) { + ag.LeftCell++; + } else if (pos.x == ag.RightCell) { + ag.RightCell--; + } else { + } + } break; + } + + switch (type) { + // Nothing to do + case TableCell::ConstantCell: break; + + case TableCell::SingularParametricCell: { + int idx = pos.y * GetTableWidth() + pos.x; + auto [DISCARD, inserted] = mName2Parameters.insert(cell.Content, idx); + + // Duplicate name + if (!inserted) { + return; + } + } break; + + case TableCell::ArrayParametricCell: { + auto ptr = AddArrayGroup(pos.y, pos.x, pos.x); + + // Duplicate name + if (ptr == nullptr) { + return; + } + } break; + } + + cell.Type = type; +} + +bool TableTemplate::UpdateParameterName(std::string_view oldName, std::string_view newName) +{ + return ::UpdateElementName(mName2Parameters, oldName, newName); +} + +int TableTemplate::GetArrayGroupCount() const +{ + return mArrayGroups.size(); +} + +const TableArrayGroup& TableTemplate::GetArrayGroup(int id) const +{ + return mArrayGroups[id]; +} + +TableArrayGroup& TableTemplate::GetArrayGroup(int id) +{ + return mArrayGroups[id]; +} + +TableArrayGroup* TableTemplate::AddArrayGroup(int row, int left, int right) +{ + // size_t max value: 18446744073709551615 + // ^~~~~~~~~~~~~~~~~~~~ 20 chars + char name[20]; + auto res = std::to_chars(std::begin(name), std::end(name), mArrayGroups.size()); + std::string_view nameStr(name, res.ptr - name); + + return AddArrayGroup(nameStr, row, left, right); +} + +TableArrayGroup* TableTemplate::AddArrayGroup(std::string_view name, int row, int left, int right) +{ + assert(row >= 0 && row < GetTableHeight()); + assert(left >= 0 && left < GetTableWidth()); + assert(right >= 0 && right < GetTableWidth()); + + // TODO check for overlap + + if (left > right) { + std::swap(left, right); + } + + auto [DISCARD, inserted] = mName2ArrayGroups.insert(name, (int)mArrayGroups.size()); + if (!inserted) { + return nullptr; + } + + mArrayGroups.push_back(TableArrayGroup{ + .Row = row, + .LeftCell = left, + .RightCell = right, + }); + auto& ag = mArrayGroups.back(); + + for (int x = left; x <= right; x++) { + auto& cell = GetCell({ x, row }); + + // Update type + cell.Type = TableCell::ArrayParametricCell; + + // Insert parameter name lookup + while (true) { + auto [DISCARD, inserted] = ag.mName2Cell.insert(cell.Content, x); + if (inserted) { + break; + } + + cell.Content += "-"; + } + } + + return &ag; +} + +bool TableTemplate::UpdateArrayGroupName(std::string_view oldName, std::string_view newName) +{ + return ::UpdateElementName(mName2ArrayGroups, oldName, newName); +} + +bool TableTemplate::ExtendArrayGroupLeft(int id, int n) +{ + assert(n > 0); + + auto& ag = mArrayGroups[id]; + ag.LeftCell -= n; + + return false; +} + +bool TableTemplate::ExtendArrayGroupRight(int id, int n) +{ + assert(n > 0); + + auto& ag = mArrayGroups[id]; + ag.RightCell += n; + + return false; +} + +TableCell* TableTemplate::FindCell(std::string_view name) +{ + auto iter = mName2Parameters.find(name); + if (iter != mName2Parameters.end()) { + return &mCells[iter.value()]; + } else { + return nullptr; + } +} + +TableArrayGroup* TableTemplate::FindArrayGroup(std::string_view name) +{ + auto iter = mName2ArrayGroups.find(name); + if (iter != mName2ArrayGroups.end()) { + return &mArrayGroups[iter.value()]; + } else { + return nullptr; + } +} + +TableTemplate::MergeCellsResult TableTemplate::MergeCells(Vec2i topLeft, Vec2i bottomRight) +{ + auto SortTwo = [](int& a, int& b) { + if (a > b) { + std::swap(a, b); + } + }; + SortTwo(topLeft.x, bottomRight.x); + SortTwo(topLeft.y, bottomRight.y); + + auto ResetProgress = [&]() { + for (int y = topLeft.y; y < bottomRight.y; ++y) { + for (int x = topLeft.x; x < bottomRight.x; ++x) { + auto& cell = GetCell({ x, y }); + cell.PrimaryCellLocation = { -1, -1 }; + } + } + }; + + for (int y = topLeft.y; y < bottomRight.y; ++y) { + for (int x = topLeft.x; x < bottomRight.x; ++x) { + auto& cell = GetCell({ x, y }); + if (cell.IsMergedCell()) { + ResetProgress(); + return MCR_CellAlreadyMerged; + } + + cell.PrimaryCellLocation = topLeft; + } + } + + auto& primaryCell = GetCell(topLeft); + primaryCell.SpanX = bottomRight.x - topLeft.x; + primaryCell.SpanY = bottomRight.y - topLeft.y; + + return MCR_Success; +} + +TableTemplate::BreakCellsResult TableTemplate::BreakCells(Vec2i topLeft) +{ + auto& primaryCell = GetCell(topLeft); + if (!primaryCell.IsMergedCell()) { + return BCR_CellNotMerged; + } + + for (int dy = 0; dy < primaryCell.SpanY; ++dy) { + for (int dx = 0; dx < primaryCell.SpanX; ++dx) { + auto& cell = GetCell({ topLeft.x + dx, topLeft.y + dy }); + cell.PrimaryCellLocation = { -1, -1 }; + } + } + + primaryCell.SpanX = 1; + primaryCell.SpanY = 1; + + return BCR_Success; +} + +lxw_workbook* TableTemplate::InstantiateToExcelWorkbook(const TableInstantiationParameters& params) const +{ + auto workbook = workbook_new("Table.xlsx"); + InstantiateToExcelWorksheet(workbook, params); + return workbook; +} + +lxw_worksheet* TableTemplate::InstantiateToExcelWorksheet(lxw_workbook* workbook, const TableInstantiationParameters& params) const +{ + auto worksheet = workbook_add_worksheet(workbook, "CpltExport.xlsx"); + + // Map: row number -> length of generated ranges + std::map generatedRanges; + + for (size_t i = 0; i < mArrayGroups.size(); ++i) { + auto& info = mArrayGroups[i]; + auto& param = params.ArrayGroups[i]; + + auto iter = generatedRanges.find(i); + if (iter != generatedRanges.end()) { + int available = iter->second; + if (available >= param.size()) { + // Current space is enough to fit in this array group, skip + continue; + } + } + + // Not enough space to fit in this array group, update (or insert) the appropriate amount of generated rows + int row = i; + int count = param.size(); + generatedRanges.try_emplace(row, count); + } + + auto GetOffset = [&](int y) -> int { + // std::find_if + int verticalOffset = 0; + for (auto it = generatedRanges.begin(); it != generatedRanges.end() && it->first < y; ++it) { + verticalOffset += it->second; + } + return verticalOffset; + }; + + auto WriteCell = [&](int row, int col, const TableCell& cell, const char* text) -> void { + if (cell.IsPrimaryCell()) { + int lastRow = row + cell.SpanY - 1; + int lastCol = col + cell.SpanX - 1; + // When both `string` and `format` are null, the top-left cell contents are untouched (what we just wrote in the above switch) + worksheet_merge_range(worksheet, row, col, lastRow, lastCol, text, nullptr); + } else { + worksheet_write_string(worksheet, row, col, text, nullptr); + } + }; + + // Write/instantiate all array groups + for (size_t i = 0; i < mArrayGroups.size(); ++i) { + auto& groupInfo = mArrayGroups[i]; + auto& groupParams = params.ArrayGroups[i]; + + int rowCellCount = groupInfo.GetCount(); + int rowCount = groupParams.size(); + int baseRowIdx = groupInfo.Row + GetOffset(groupInfo.Row); + + // For each row that would be generated + for (int rowIdx = 0; rowIdx < rowCount; ++rowIdx) { + auto& row = groupParams[rowIdx]; + + // For each cell in the row + for (int rowCellIdx = 0; rowCellIdx < rowCellCount; ++rowCellIdx) { + // TODO support merged cells in array groups + worksheet_write_string(worksheet, baseRowIdx + rowIdx, rowCellIdx, row[rowCellIdx].c_str(), nullptr); + } + } + } + + int tableWidth = GetTableWidth(); + int tableHeight = GetTableHeight(); + + // Write all regular and singular parameter cells + for (int y = 0; y < tableHeight; ++y) { + for (int x = 0; x < tableWidth; ++x) { + auto& cell = GetCell({ x, y }); + + if (!cell.IsDataHoldingCell()) { + continue; + } + + switch (cell.Type) { + case TableCell::ConstantCell: { + int row = y + GetOffset(y); + int col = x; + + WriteCell(row, col, cell, cell.Content.c_str()); + } break; + + case TableCell::SingularParametricCell: { + int row = y + GetOffset(y); + int col = x; + + auto iter = params.SingularCells.find({ x, y }); + if (iter != params.SingularCells.end()) { + WriteCell(row, col, cell, iter.value().c_str()); + } + } break; + + // See loop above that processes whole array groups at the same time + case TableCell::ArrayParametricCell: break; + } + } + } + + return worksheet; +} + +class TableTemplate::Private +{ +public: + template + static void OperateStream(TTableTemplate& table, TProxy& proxy) + { + proxy.template ObjectAdapted>(table.mColumnWidths); + proxy.template ObjectAdapted>(table.mRowHeights); + proxy.template ObjectAdapted>(table.mCells); + proxy.template ObjectAdapted>(table.mArrayGroups); + proxy.template ObjectAdapted>(table.mName2Parameters); + proxy.template ObjectAdapted>(table.mName2ArrayGroups); + } +}; + +void TableTemplate::ReadFromDataStream(InputDataStream& stream) +{ + Private::OperateStream(*this, stream); +} + +void TableTemplate::WriteToDataStream(OutputDataStream& stream) const +{ + Private::OperateStream(*this, stream); +} diff --git a/app/source/Cplt/Model/Template/TableTemplate.hpp b/app/source/Cplt/Model/Template/TableTemplate.hpp new file mode 100644 index 0000000..3e931d4 --- /dev/null +++ b/app/source/Cplt/Model/Template/TableTemplate.hpp @@ -0,0 +1,223 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +class TableCell +{ +public: + enum TextAlignment + { + /// For horizontal alignment, this means align left. For vertical alignment, this means align top. + AlignAxisMin, + /// Align middle of the text to the middle of the axis. + AlignCenter, + /// For horizontal alignment, this means align right. For vertical alignment, this means align bottom. + AlignAxisMax, + }; + + enum CellType + { + ConstantCell, + SingularParametricCell, + ArrayParametricCell, + }; + +public: + /// Display content of this cell. This doesn't necessarily have to line up with the parameter name (if this cell is one). + std::string Content; + Vec2i Location; + /// Location of the primary (top left) cell, if this cell is a part of a merged group. + /// Otherwise, either component of this field shall be -1. + Vec2i PrimaryCellLocation{ -1, -1 }; + int SpanX = 0; + int SpanY = 0; + TextAlignment HorizontalAlignment = AlignCenter; + TextAlignment VerticalAlignment = AlignCenter; + CellType Type = ConstantCell; + /// The id of the group description object, if this cell isn't a constant or singular parameter cell. Otherwise, this value is -1. + int DataId = -1; + +public: + /// Return whether this cell holds meaningful data, i.e. true when this cell is either unmerged or the primary cell of a merged range. + bool IsDataHoldingCell() const; + /// Return whether this cell is the primary (i.e. top left) cell of a merged range or not. + bool IsPrimaryCell() const; + /// Return whether this cell is a part of a merged range or not. Includes the primary cell. + bool IsMergedCell() const; + + void ReadFromDataStream(InputDataStream& stream); + void WriteToDataStream(OutputDataStream& stream) const; +}; + +// TODO support reverse (bottom to top) filling order +// TODO support horizontal filling order + +/// Parameter group information for a grouped array of cells. When instantiated, an array of 0 or more +/// elements shall be provided by the user, which will replace the group of templated cells with a list +/// of rows, each instantiated with the n-th element in the provided array. +/// \code +/// [["foo", "bar", "foobar"], +/// ["a", "b", c"], +/// ["1", "2", "3"], +/// ["x", "y", "z"]] +/// // ... may be more +/// \endcode +/// This would create 4 rows of data in the place of the original parameter group. +/// +/// If more than one array parameter groups are on the same row, they would share space between each other: +/// \code +/// | 2 elements was fed to it +/// | | 1 element was fed to it +/// V V +/// {~~~~~~~~~~~~~~~~}{~~~~~~~~~~~~~~} +/// +------+---------+---------------+ +/// | Foo | Example | Another group | +/// +------+---------+---------------+ +/// | Cool | Example | | +/// +------+---------+---------------+ +/// \endcode +/// +/// \see TableCell +/// \see TableInstantiationParameters +/// \see TableTemplate +class TableArrayGroup +{ +public: + /// Parameter name mapped to cell location (index from LeftCell). + tsl::array_map mName2Cell; + int Row; + /// Leftmost cell in this group + int LeftCell; + /// Rightmost cell in this group + int RightCell; + +public: + Vec2i GetLeftCell() const; + Vec2i GetRightCell() const; + int GetCount() const; + + /// Find the location of the cell within this array group that has the given name. + Vec2i FindCell(std::string_view name); + bool UpdateCellName(std::string_view oldName, std::string_view newName); + + void ReadFromDataStream(InputDataStream& stream); + void WriteToDataStream(OutputDataStream& stream) const; +}; + +// Forward declaration of libxlsxwriter structs +struct lxw_workbook; +struct lxw_worksheet; + +/// An object containing the necessary information to instantiate a table template. +/// \see TableTemplate +class TableInstantiationParameters +{ +private: + const TableTemplate* mTable; + +public: + tsl::robin_map SingularCells; + + using ArrayGroupRow = std::vector; + using ArrayGroupData = std::vector; + std::vector ArrayGroups; + +public: + TableInstantiationParameters(const TableTemplate& table); + + TableInstantiationParameters& ResetTable(const TableTemplate& newTable); + TableInstantiationParameters RebindTable(const TableTemplate& newTable) const; + + const TableTemplate& GetTable() const; +}; + +/// A table template, where individual cells can be filled by workflows instantiating this template. Merged cells, +/// parametric rows/columns, and grids are also supported. +/// +/// This current supports exporting to xlsx files. +class TableTemplate : public Template +{ + friend class TableSingleParamsIter; + friend class TableArrayGroupsIter; + class Private; + +private: + /// Map from parameter name to index of the parameter cell (stored in mCells). + tsl::array_map mName2Parameters; + /// Map from array group name to the index of the array group (stored in mArrayGroups). + tsl::array_map mName2ArrayGroups; + std::vector mCells; + std::vector mArrayGroups; + std::vector mRowHeights; + std::vector mColumnWidths; + +public: + static bool IsInstance(const Template* tmpl); + TableTemplate(); + + int GetTableWidth() const; + int GetTableHeight() const; + void Resize(int newWidth, int newHeight); + + int GetRowHeight(int row) const; + void SetRowHeight(int row, int height); + int GetColumnWidth(int column) const; + void SetColumnWidth(int column, int width); + + const TableCell& GetCell(Vec2i pos) const; + TableCell& GetCell(Vec2i pos); + ///
    + ///
  • In case of becoming a SingularParametricCell: the parameter name is filled with TableCell::Content. + ///
  • In case of becoming a ArrayGroupParametricCell: the array group name is automatically generated as the nth group it would be come. + /// i.e., if there aRe currently 3 groups, the newly created group would be named "4". + /// If this name collides with an existing group, hyphens \c - will be append to the name until no collision happens. + ///
+ void SetCellType(Vec2i pos, TableCell::CellType type); + + /// Updates the parameter cell to a new name. Returns true on success and false on failure (param not found or name duplicates). + bool UpdateParameterName(std::string_view oldName, std::string_view newName); + + int GetArrayGroupCount() const; + const TableArrayGroup& GetArrayGroup(int id) const; + TableArrayGroup& GetArrayGroup(int id); + TableArrayGroup* AddArrayGroup(int row, int left, int right); + TableArrayGroup* AddArrayGroup(std::string_view name, int row, int left, int right); + bool UpdateArrayGroupName(std::string_view oldName, std::string_view newName); + bool ExtendArrayGroupLeft(int id, int n); + bool ExtendArrayGroupRight(int id, int n); + + /// Find a singular parameter cell by its name. This does not include cells within an array group. + TableCell* FindCell(std::string_view name); + + /// Find an array group by its name. + TableArrayGroup* FindArrayGroup(std::string_view name); + + enum MergeCellsResult + { + MCR_CellAlreadyMerged, + MCR_Success, + }; + MergeCellsResult MergeCells(Vec2i topLeft, Vec2i bottomRight); + + enum BreakCellsResult + { + BCR_CellNotMerged, + BCR_Success, + }; + BreakCellsResult BreakCells(Vec2i topLeft); + + lxw_workbook* InstantiateToExcelWorkbook(const TableInstantiationParameters& params) const; + lxw_worksheet* InstantiateToExcelWorksheet(lxw_workbook* workbook, const TableInstantiationParameters& params) const; + + void ReadFromDataStream(InputDataStream& stream) override; + void WriteToDataStream(OutputDataStream& stream) const override; +}; diff --git a/app/source/Cplt/Model/Template/TableTemplateIterator.cpp b/app/source/Cplt/Model/Template/TableTemplateIterator.cpp new file mode 100644 index 0000000..19e30b9 --- /dev/null +++ b/app/source/Cplt/Model/Template/TableTemplateIterator.cpp @@ -0,0 +1,52 @@ +#include "TableTemplateIterator.hpp" + +TableSingleParamsIter::TableSingleParamsIter(TableTemplate& tmpl) + : mTemplate{ &tmpl } + , mIter{ tmpl.mName2Parameters.begin() } +{ +} + +bool TableSingleParamsIter::HasNext() const +{ + return mIter != mTemplate->mName2Parameters.end(); +} + +TableCell& TableSingleParamsIter::Next() +{ + int id = mIter.value(); + ++mIter; + + return mTemplate->mCells[id]; +} + +TableArrayGroupsIter::TableArrayGroupsIter(TableTemplate& tmpl) + : mTemplate{ &tmpl } + , mIter{ tmpl.mName2ArrayGroups.begin() } +{ +} + +bool TableArrayGroupsIter::HasNext() const +{ + return mIter != mTemplate->mName2ArrayGroups.end(); +} + +TableArrayGroup& TableArrayGroupsIter::Peek() const +{ + int id = mIter.value(); + return mTemplate->mArrayGroups[id]; +} + +std::string_view TableArrayGroupsIter::PeekName() const +{ + return mIter.key_sv(); +} + +const char* TableArrayGroupsIter::PeekNameCStr() const +{ + return mIter.key(); +} + +void TableArrayGroupsIter::Next() +{ + ++mIter; +} diff --git a/app/source/Cplt/Model/Template/TableTemplateIterator.hpp b/app/source/Cplt/Model/Template/TableTemplateIterator.hpp new file mode 100644 index 0000000..c4b5bf9 --- /dev/null +++ b/app/source/Cplt/Model/Template/TableTemplateIterator.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include +#include + +#include + +class TableSingleParamsIter +{ +private: + TableTemplate* mTemplate; + tsl::array_map::iterator mIter; + +public: + TableSingleParamsIter(TableTemplate& tmpl); + + bool HasNext() const; + TableCell& Next(); +}; + +class TableArrayGroupsIter +{ +private: + TableTemplate* mTemplate; + tsl::array_map::iterator mIter; + +public: + TableArrayGroupsIter(TableTemplate& tmpl); + + bool HasNext() const; + TableArrayGroup& Peek() const; + std::string_view PeekName() const; + const char* PeekNameCStr() const; + void Next(); +}; diff --git a/app/source/Cplt/Model/Template/Template.hpp b/app/source/Cplt/Model/Template/Template.hpp new file mode 100644 index 0000000..cf926d0 --- /dev/null +++ b/app/source/Cplt/Model/Template/Template.hpp @@ -0,0 +1,68 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include + +class Template : public Asset +{ +public: + enum Kind + { + KD_Table, + + InvalidKind, + KindCount = InvalidKind, + }; + + using CategoryType = TemplateAssetList; + +private: + Kind mKind; + +public: + static const char* FormatKind(Kind kind); + static std::unique_ptr