diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f219210f..e5fb92c74 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,8 +43,9 @@ add_compile_definitions($<$:NOMINMAX>) add_compile_options($<$:--Werror=all-warnings>) add_executable(alien) -add_executable(tests) add_executable(cli) +add_executable(EngineTests) +add_executable(NetworkTests) find_package(CUDAToolkit) find_package(Boost REQUIRED) @@ -62,12 +63,14 @@ find_package(CLI11 CONFIG REQUIRED) add_subdirectory(external/ImFileDialog) add_subdirectory(source/Base) +add_subdirectory(source/Cli) add_subdirectory(source/EngineGpuKernels) add_subdirectory(source/EngineImpl) add_subdirectory(source/EngineInterface) add_subdirectory(source/EngineTests) add_subdirectory(source/Gui) -add_subdirectory(source/Cli) +add_subdirectory(source/Network) +add_subdirectory(source/NetworkTests) # Copy resources to the build location add_custom_command( diff --git a/README.md b/README.md index cc52d8806..9dc023122 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ Further information and artwork: An Nvidia graphics card with compute capability 6.0 or higher is needed. Please check [https://en.wikipedia.org/wiki/CUDA#GPUs_supported](https://en.wikipedia.org/wiki/CUDA#GPUs_supported). # 💽 Installer -Installer for Windows: [alien-installer.msi](https://alien-project.org/media/files/alien-installer.msi) (Updated: 2023-12-10) +Installer for Windows: [alien-installer.msi](https://alien-project.org/media/files/alien-installer.msi) (Updated: 2023-12-29) In the case that the program crashes for an unknown reason, please refer to the troubleshooting section in [alien-project.org/downloads.html](https://alien-project.org/downloads.html). diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 7bcd40622..e88e22aba 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,5 +1,19 @@ # Release notes +## [4.6.0] - 2023-12-29 +### Added +- gui/browser: support for displaying folders and subfolders +- gui/browser: folders for simulations and genomes are automatically created by parsing their names for `/` +- gui/browser: allow uploading to a selected folder +- gui/browser: show number of simulations per folder + +### Changed +- gui/browser: tree view instead of a pure tabular view +- gui/browser: simulations and genomes can be selected for user actions (e.g. deletion) + +### Removed +- gui/browser: column for actions removed + ## [4.5.1] - 2023-12-16 ### Added - new simulation parameters to reduce energy particle absorption for cells with fewer connections diff --git a/imgui.ini b/imgui.ini index 04b4cd510..8a6b7f398 100644 --- a/imgui.ini +++ b/imgui.ini @@ -274,8 +274,8 @@ Size=556,448 Collapsed=0 [Window][Browser] -Pos=177,203 -Size=1287,597 +Pos=172,78 +Size=1252,825 Collapsed=0 [Window][Login] @@ -294,8 +294,8 @@ Size=463,153 Collapsed=0 [Window][Upload simulation] -Pos=733,403 -Size=400,218 +Pos=641,346 +Size=454,354 Collapsed=0 [Window][Upload genome] diff --git a/source/Base/CMakeLists.txt b/source/Base/CMakeLists.txt index 86eece9fa..e673863b7 100644 --- a/source/Base/CMakeLists.txt +++ b/source/Base/CMakeLists.txt @@ -1,5 +1,5 @@ -add_library(alien_base_lib +add_library(Base Definitions.cpp Definitions.h Exceptions.h @@ -25,8 +25,8 @@ add_library(alien_base_lib VersionChecker.cpp VersionChecker.h) -target_link_libraries(alien_base_lib Boost::boost) +target_link_libraries(Base Boost::boost) if (MSVC) - target_compile_options(alien_base_lib PRIVATE "/MP") + target_compile_options(Base PRIVATE "/MP") endif() diff --git a/source/Base/LoggingService.h b/source/Base/LoggingService.h index 8bc513d46..5b3badeda 100644 --- a/source/Base/LoggingService.h +++ b/source/Base/LoggingService.h @@ -27,6 +27,8 @@ class LoggingService void unregisterCallBack(LoggingCallBack* callback); private: + LoggingService() = default; + std::vector _callbacks; std::mutex _mutex; }; diff --git a/source/Base/Resources.h b/source/Base/Resources.h index bbf9a8177..dc55a29f4 100644 --- a/source/Base/Resources.h +++ b/source/Base/Resources.h @@ -2,7 +2,7 @@ namespace Const { - std::string const ProgramVersion = "4.5.1"; + std::string const ProgramVersion = "4.6.0"; std::string const DiscordLink = "https://discord.gg/7bjyZdXXQ2"; std::string const BasePath = "resources/"; diff --git a/source/Cli/CMakeLists.txt b/source/Cli/CMakeLists.txt index a45b59fa1..50f6ee80c 100644 --- a/source/Cli/CMakeLists.txt +++ b/source/Cli/CMakeLists.txt @@ -2,10 +2,10 @@ target_sources(cli PUBLIC Main.cpp) -target_link_libraries(cli alien_base_lib) -target_link_libraries(cli alien_engine_gpu_kernels_lib) -target_link_libraries(cli alien_engine_impl_lib) -target_link_libraries(cli alien_engine_interface_lib) +target_link_libraries(cli Base) +target_link_libraries(cli EngineGpuKernels) +target_link_libraries(cli EngineImpl) +target_link_libraries(cli EngineInterface) target_link_libraries(cli CUDA::cudart_static) target_link_libraries(cli CUDA::cuda_driver) diff --git a/source/EngineGpuKernels/CMakeLists.txt b/source/EngineGpuKernels/CMakeLists.txt index 3b0e24e34..4a2474816 100644 --- a/source/EngineGpuKernels/CMakeLists.txt +++ b/source/EngineGpuKernels/CMakeLists.txt @@ -1,5 +1,5 @@ -add_library(alien_engine_gpu_kernels_lib +add_library(EngineGpuKernels Array.cuh AttackerProcessor.cuh Base.cuh @@ -92,8 +92,8 @@ add_library(alien_engine_gpu_kernels_lib Util.cuh ) -target_link_libraries(alien_engine_gpu_kernels_lib alien_base_lib) -target_link_libraries(alien_engine_gpu_kernels_lib alien_engine_interface_lib) +target_link_libraries(EngineGpuKernels Base) +target_link_libraries(EngineGpuKernels EngineInterface) # See https://gitlab.kitware.com/cmake/cmake/-/issues/17520 -set_property(TARGET alien_engine_gpu_kernels_lib PROPERTY CUDA_RESOLVE_DEVICE_SYMBOLS ON) \ No newline at end of file +set_property(TARGET EngineGpuKernels PROPERTY CUDA_RESOLVE_DEVICE_SYMBOLS ON) \ No newline at end of file diff --git a/source/EngineImpl/CMakeLists.txt b/source/EngineImpl/CMakeLists.txt index 4ff382504..4965191d8 100644 --- a/source/EngineImpl/CMakeLists.txt +++ b/source/EngineImpl/CMakeLists.txt @@ -1,5 +1,5 @@ -add_library(alien_engine_impl_lib +add_library(EngineImpl AccessDataTOCache.cpp AccessDataTOCache.h DescriptionConverter.cpp @@ -10,12 +10,12 @@ add_library(alien_engine_impl_lib SimulationControllerImpl.cpp SimulationControllerImpl.h) -target_link_libraries(alien_engine_impl_lib alien_base_lib) -target_link_libraries(alien_engine_impl_lib alien_engine_gpu_kernels_lib) +target_link_libraries(EngineImpl Base) +target_link_libraries(EngineImpl EngineGpuKernels) -target_link_libraries(alien_engine_impl_lib CUDA::cudart_static) -target_link_libraries(alien_engine_impl_lib Boost::boost) +target_link_libraries(EngineImpl CUDA::cudart_static) +target_link_libraries(EngineImpl Boost::boost) if (MSVC) - target_compile_options(alien_engine_impl_lib PRIVATE "/MP") + target_compile_options(EngineImpl PRIVATE "/MP") endif() diff --git a/source/EngineInterface/CMakeLists.txt b/source/EngineInterface/CMakeLists.txt index 110973033..4318deeeb 100644 --- a/source/EngineInterface/CMakeLists.txt +++ b/source/EngineInterface/CMakeLists.txt @@ -1,5 +1,5 @@ -add_library(alien_engine_interface_lib +add_library(EngineInterface ArraySizes.h AuxiliaryData.h AuxiliaryDataParserService.cpp @@ -49,13 +49,13 @@ add_library(alien_engine_interface_lib StatisticsHistory.h ZoomLevels.h) -target_link_libraries(alien_engine_interface_lib Boost::boost) -target_link_libraries(alien_engine_interface_lib cereal) +target_link_libraries(EngineInterface Boost::boost) +target_link_libraries(EngineInterface cereal) target_link_libraries(alien ZLIB::ZLIB) find_path(ZSTR_INCLUDE_DIRS "zstr.hpp") -target_include_directories(alien_engine_interface_lib PRIVATE ${ZSTR_INCLUDE_DIRS}) +target_include_directories(EngineInterface PRIVATE ${ZSTR_INCLUDE_DIRS}) if (MSVC) - target_compile_options(alien_engine_interface_lib PRIVATE "/MP") + target_compile_options(EngineInterface PRIVATE "/MP") endif() diff --git a/source/EngineInterface/Colors.h b/source/EngineInterface/Colors.h index e307ab94a..cd63613ed 100644 --- a/source/EngineInterface/Colors.h +++ b/source/EngineInterface/Colors.h @@ -27,7 +27,7 @@ namespace Const uint32_t const CellFunctionInfoColor = 0x404090; uint32_t const BranchNumberInfoColor = 0x000000; - uint32_t const NothingnessColor = 0x000000; + uint32_t const VoidColor = 0x000000; } template diff --git a/source/EngineTests/CMakeLists.txt b/source/EngineTests/CMakeLists.txt index e24da2632..7b456d593 100644 --- a/source/EngineTests/CMakeLists.txt +++ b/source/EngineTests/CMakeLists.txt @@ -1,4 +1,4 @@ -target_sources(tests +target_sources(EngineTests PUBLIC AttackerTests.cpp CellConnectionTests.cpp @@ -20,20 +20,20 @@ PUBLIC Testsuite.cpp TransmitterTests.cpp) -target_link_libraries(tests alien_base_lib) -target_link_libraries(tests alien_engine_gpu_kernels_lib) -target_link_libraries(tests alien_engine_impl_lib) -target_link_libraries(tests alien_engine_interface_lib) +target_link_libraries(EngineTests Base) +target_link_libraries(EngineTests EngineGpuKernels) +target_link_libraries(EngineTests EngineImpl) +target_link_libraries(EngineTests EngineInterface) -target_link_libraries(tests CUDA::cudart_static) -target_link_libraries(tests CUDA::cuda_driver) -target_link_libraries(tests Boost::boost) -target_link_libraries(tests OpenGL::GL OpenGL::GLU) -target_link_libraries(tests GLEW::GLEW) -target_link_libraries(tests glfw) -target_link_libraries(tests glad::glad) -target_link_libraries(tests GTest::GTest GTest::Main) +target_link_libraries(EngineTests CUDA::cudart_static) +target_link_libraries(EngineTests CUDA::cuda_driver) +target_link_libraries(EngineTests Boost::boost) +target_link_libraries(EngineTests OpenGL::GL OpenGL::GLU) +target_link_libraries(EngineTests GLEW::GLEW) +target_link_libraries(EngineTests glfw) +target_link_libraries(EngineTests glad::glad) +target_link_libraries(EngineTests GTest::GTest GTest::Main) if (MSVC) - target_compile_options(tests PRIVATE "/MP") + target_compile_options(EngineTests PRIVATE "/MP") endif() diff --git a/source/Gui/ActivateUserDialog.cpp b/source/Gui/ActivateUserDialog.cpp index ac82009c8..e9966984e 100644 --- a/source/Gui/ActivateUserDialog.cpp +++ b/source/Gui/ActivateUserDialog.cpp @@ -3,22 +3,20 @@ #include #include "EngineInterface/SimulationController.h" +#include "Network/NetworkService.h" #include "AlienImGui.h" #include "MessageDialog.h" -#include "NetworkController.h" #include "BrowserWindow.h" #include "CreateUserDialog.h" #include "StyleRepository.h" _ActivateUserDialog::_ActivateUserDialog( SimulationController const& simController, - BrowserWindow const& browserWindow, - NetworkController const& networkController) + BrowserWindow const& browserWindow) : _AlienDialog("Activate user") , _simController(simController) , _browserWindow(browserWindow) - , _networkController(networkController) {} _ActivateUserDialog::~_ActivateUserDialog() {} @@ -81,10 +79,11 @@ void _ActivateUserDialog::processIntern() void _ActivateUserDialog::onActivateUser() { - auto result = _networkController->activateUser(_userName, _password, _userInfo, _confirmationCode); + auto& networkService = NetworkService::getInstance(); + auto result = networkService.activateUser(_userName, _password, _userInfo, _confirmationCode); if (result) { LoginErrorCode errorCode; - result |= _networkController->login(errorCode, _userName, _password, _userInfo); + result |= networkService.login(errorCode, _userName, _password, _userInfo); } if (!result) { MessageDialog::getInstance().information("Error", "An error occurred on the server. Your entered code may be incorrect.\nPlease try to register again."); diff --git a/source/Gui/ActivateUserDialog.h b/source/Gui/ActivateUserDialog.h index 15d4b5ae9..305f091d4 100644 --- a/source/Gui/ActivateUserDialog.h +++ b/source/Gui/ActivateUserDialog.h @@ -1,13 +1,14 @@ #pragma once +#include "Network/NetworkService.h" + #include "AlienDialog.h" #include "Definitions.h" -#include "NetworkController.h" class _ActivateUserDialog : public _AlienDialog { public: - _ActivateUserDialog(SimulationController const& simController, BrowserWindow const& browserWindow, NetworkController const& networkController); + _ActivateUserDialog(SimulationController const& simController, BrowserWindow const& browserWindow); ~_ActivateUserDialog(); void registerCyclicReferences(CreateUserDialogWeakPtr const& createUserDialog); @@ -20,7 +21,6 @@ class _ActivateUserDialog : public _AlienDialog SimulationController _simController; BrowserWindow _browserWindow; - NetworkController _networkController; CreateUserDialogWeakPtr _createUserDialog; std::string _userName; diff --git a/source/Gui/AlienImGui.cpp b/source/Gui/AlienImGui.cpp index 7b321b795..789eceb80 100644 --- a/source/Gui/AlienImGui.cpp +++ b/source/Gui/AlienImGui.cpp @@ -181,7 +181,7 @@ void AlienImGui::InputFloat2(InputFloat2Parameters const& parameters, float& val ImGuiInputTextFlags flags = parameters._readOnly ? ImGuiInputTextFlags_ReadOnly : ImGuiInputTextFlags_None; ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - textWidth); - float value[2]; + static float value[2]; value[0] = value1; value[1] = value2; ImGui::InputFloat2(("##" + parameters._name).c_str(), value, parameters._format.c_str(), flags); diff --git a/source/Gui/BrowserWindow.cpp b/source/Gui/BrowserWindow.cpp index 0e1b2f52f..5a285a492 100644 --- a/source/Gui/BrowserWindow.cpp +++ b/source/Gui/BrowserWindow.cpp @@ -21,11 +21,13 @@ #include "EngineInterface/GenomeDescriptionService.h" #include "EngineInterface/SerializerService.h" #include "EngineInterface/SimulationController.h" +#include "Network/NetworkResourceService.h" +#include "Network/NetworkService.h" +#include "Network/NetworkResourceParserService.h" +#include "Network/NetworkResourceTreeTO.h" #include "AlienImGui.h" #include "StyleRepository.h" -#include "NetworkDataParser.h" -#include "NetworkController.h" #include "StatisticsWindow.h" #include "Viewport.h" #include "TemporalControlWindow.h" @@ -53,14 +55,12 @@ namespace _BrowserWindow::_BrowserWindow( SimulationController const& simController, - NetworkController const& networkController, StatisticsWindow const& statisticsWindow, Viewport const& viewport, TemporalControlWindow const& temporalControlWindow, EditorController const& editorController) : _AlienWindow("Browser", "windows.browser", true) , _simController(simController) - , _networkController(networkController) , _statisticsWindow(statisticsWindow) , _viewport(viewport) , _temporalControlWindow(temporalControlWindow) @@ -83,6 +83,11 @@ _BrowserWindow::~_BrowserWindow() GlobalSettings::getInstance().setBoolState("windows.browser.show community creations", _showCommunityCreations); GlobalSettings::getInstance().setBoolState("windows.browser.first start", false); GlobalSettings::getInstance().setFloatState("windows.browser.user table width", _userTableWidth); + GlobalSettings::getInstance().setStringState( + "windows.browser.simulations.collapsed folders", NetworkResourceService::convertFolderNamesToSettings(_simulations.collapsedFolderNames)); + GlobalSettings::getInstance().setStringState( + "windows.browser.genomes.collapsed folders", NetworkResourceService::convertFolderNamesToSettings(_genomes.collapsedFolderNames)); + NetworkService::getInstance().shutdown(); } void _BrowserWindow::registerCyclicReferences(LoginDialogWeakPtr const& loginDialog, UploadSimulationDialogWeakPtr const& uploadSimulationDialog) @@ -92,6 +97,16 @@ void _BrowserWindow::registerCyclicReferences(LoginDialogWeakPtr const& loginDia auto firstStart = GlobalSettings::getInstance().getBoolState("windows.browser.first start", true); refreshIntern(firstStart); + + auto initialCollapsedSimulationFolders = NetworkResourceService::convertFolderNamesToSettings(NetworkResourceService::calcInitialCollapsedFolderNames(_simulations.rawTOs)); + auto collapsedSimulationFolders = GlobalSettings::getInstance().getStringState("windows.browser.simulations.collapsed folders", initialCollapsedSimulationFolders); + _simulations.collapsedFolderNames = NetworkResourceService::convertSettingsToFolderNames(collapsedSimulationFolders); + + auto initialCollapsedGenomeFolders = + NetworkResourceService::convertFolderNamesToSettings(NetworkResourceService::calcInitialCollapsedFolderNames(_simulations.rawTOs)); + auto collapsedGenomeFolders = + GlobalSettings::getInstance().getStringState("windows.browser.genomes.collapsed folders", initialCollapsedGenomeFolders); + _genomes.collapsedFolderNames = NetworkResourceService::convertSettingsToFolderNames(collapsedGenomeFolders); } void _BrowserWindow::onRefresh() @@ -102,35 +117,37 @@ void _BrowserWindow::onRefresh() void _BrowserWindow::refreshIntern(bool withRetry) { try { - bool success = _networkController->getRemoteSimulationList(_rawRemoteDataList, withRetry); - success &= _networkController->getUserList(_userList, withRetry); + auto& networkService = NetworkService::getInstance(); + networkService.refreshLogin(); + + bool success = networkService.getRemoteSimulationList(_unfilteredRawTOs, withRetry); + success &= networkService.getUserList(_userTOs, withRetry); if (!success) { if (withRetry) { MessageDialog::getInstance().information("Error", "Failed to retrieve browser data. Please try again."); } } else { - _numSimulations = 0; - _numGenomes = 0; - for (auto const& entry : _rawRemoteDataList) { - if (entry.type == DataType_Simulation) { - ++_numSimulations; + _simulations.numResources = 0; + _genomes.numResources = 0; + for (auto const& entry : _unfilteredRawTOs) { + if (entry->type == NetworkResourceType_Simulation) { + ++_simulations.numResources; } else { - ++_numGenomes; + ++_genomes.numResources; } } } - calcFilteredSimulationAndGenomeLists(); + filterRawTOs(); + scheduleCreateTreeTOs(); - if (_networkController->getLoggedInUserName()) { - if (!_networkController->getEmojiTypeBySimId(_ownEmojiTypeBySimId)) { + if (networkService.getLoggedInUserName()) { + if (!networkService.getEmojiTypeBySimId(_ownEmojiTypeBySimId)) { MessageDialog::getInstance().information("Error", "Failed to retrieve browser data. Please try again."); } } else { _ownEmojiTypeBySimId.clear(); } - - sortSimulationList(); sortUserList(); } catch (std::exception const& e) { if (withRetry) { @@ -208,13 +225,16 @@ void _BrowserWindow::processBackground() void _BrowserWindow::processToolbar() { + auto& networkService = NetworkService::getInstance(); + std::string resourceTypeString = _selectedDataType == NetworkResourceType_Simulation ? "simulation" : "genome"; + if (AlienImGui::ToolbarButton(ICON_FA_SYNC)) { onRefresh(); } AlienImGui::Tooltip("Refresh"); ImGui::SameLine(); - ImGui::BeginDisabled(_networkController->getLoggedInUserName().has_value()); + ImGui::BeginDisabled(networkService.getLoggedInUserName().has_value()); if (AlienImGui::ToolbarButton(ICON_FA_SIGN_IN_ALT)) { if (auto loginDialog = _loginDialog.lock()) { loginDialog->open(); @@ -224,10 +244,10 @@ void _BrowserWindow::processToolbar() AlienImGui::Tooltip("Login or register"); ImGui::SameLine(); - ImGui::BeginDisabled(!_networkController->getLoggedInUserName()); + ImGui::BeginDisabled(!networkService.getLoggedInUserName()); if (AlienImGui::ToolbarButton(ICON_FA_SIGN_OUT_ALT)) { if (auto loginDialog = _loginDialog.lock()) { - _networkController->logout(); + networkService.logout(); onRefresh(); } } @@ -238,16 +258,35 @@ void _BrowserWindow::processToolbar() AlienImGui::ToolbarSeparator(); ImGui::SameLine(); - if (AlienImGui::ToolbarButton(ICON_FA_SHARE_ALT)) { - _uploadSimulationDialog.lock()->open(_selectedDataType); + if (AlienImGui::ToolbarButton(ICON_FA_UPLOAD)) { + std::string prefix = [&] { + if (_selectedResource == nullptr || _selectedResource->isLeaf()) { + return std::string(); + } + return NetworkResourceService::concatenateFolderNames(_selectedResource->folderNames, true); + }(); + _uploadSimulationDialog.lock()->open(_selectedDataType, prefix); } - std::string dataType = _selectedDataType == DataType_Simulation - ? "simulation" - : "genome"; AlienImGui::Tooltip( - "Share your " + dataType + " with other users:\nYour current " + dataType + " will be uploaded to the server and made visible in the browser."); + "Share your current " + resourceTypeString + " with other users:\nThe " + resourceTypeString + + " will be uploaded to the server and made visible in the browser.\nIf you have already selected a folder, your " + resourceTypeString + + " will be uploaded there."); + + ImGui::SameLine(); + ImGui::BeginDisabled( + _selectedResource == nullptr || !_selectedResource->isLeaf() + || _selectedResource->getLeaf().rawTO->userName != networkService.getLoggedInUserName().value_or("")); + if (AlienImGui::ToolbarButton(ICON_FA_TRASH)) { + onDeleteItem(_selectedResource->getLeaf()); + _selectedResource = nullptr; + } + ImGui::EndDisabled(); + AlienImGui::Tooltip("Delete selected " + resourceTypeString); #ifdef _WIN32 + ImGui::SameLine(); + AlienImGui::ToolbarSeparator(); + ImGui::SameLine(); if (AlienImGui::ToolbarButton(ICON_FA_COMMENTS)) { openWeblink(Const::DiscordLink); @@ -261,101 +300,86 @@ void _BrowserWindow::processToolbar() void _BrowserWindow::processSimulationList() { ImGui::PushID("SimulationList"); - _selectedDataType = DataType_Simulation; - auto styleRepository = StyleRepository::getInstance(); + _selectedDataType = NetworkResourceType_Simulation; static ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Sortable | ImGuiTableFlags_SortMulti | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_ScrollY | ImGuiTableFlags_ScrollX; - if (ImGui::BeginTable("Browser", 12, flags, ImVec2(0, 0), 0.0f)) { - ImGui::TableSetupColumn( - "Actions", - ImGuiTableColumnFlags_PreferSortDescending | ImGuiTableColumnFlags_WidthFixed, - scale(90.0f), - RemoteSimulationDataColumnId_Actions); + if (ImGui::BeginTable("Browser", 11, flags, ImVec2(0, 0), 0.0f)) { + ImGui::TableSetupColumn("Simulation", ImGuiTableColumnFlags_WidthFixed, scale(210.0f), NetworkResourceColumnId_SimulationName); + ImGui::TableSetupColumn("Description", ImGuiTableColumnFlags_WidthFixed, scale(200.0f), NetworkResourceColumnId_Description); + ImGui::TableSetupColumn("Reactions", ImGuiTableColumnFlags_WidthFixed, scale(120.0f), NetworkResourceColumnId_Likes); ImGui::TableSetupColumn( "Timestamp", ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_PreferSortDescending, scale(135.0f), - RemoteSimulationDataColumnId_Timestamp); - ImGui::TableSetupColumn( - "User name", - ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed, - styleRepository.scale(120.0f), - RemoteSimulationDataColumnId_UserName); - ImGui::TableSetupColumn( - "Simulation name", - ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed, - styleRepository.scale(160.0f), - RemoteSimulationDataColumnId_SimulationName); - ImGui::TableSetupColumn( - "Description", - ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed, - styleRepository.scale(120.0f), - RemoteSimulationDataColumnId_Description); - ImGui::TableSetupColumn( - "Reactions", - ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed, - styleRepository.scale(120.0f), - RemoteSimulationDataColumnId_Likes); - ImGui::TableSetupColumn("Downloads", ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed, 0.0f, RemoteSimulationDataColumnId_NumDownloads); - ImGui::TableSetupColumn("Width", ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed, 0.0f, RemoteSimulationDataColumnId_Width); - ImGui::TableSetupColumn("Height", ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed, 0.0f, RemoteSimulationDataColumnId_Height); - ImGui::TableSetupColumn("Objects", ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed, 0.0f, RemoteSimulationDataColumnId_Particles); - ImGui::TableSetupColumn("File size", ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed, 0.0f, RemoteSimulationDataColumnId_FileSize); - ImGui::TableSetupColumn("Version", ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed, 0.0f, RemoteSimulationDataColumnId_Version); + NetworkResourceColumnId_Timestamp); + ImGui::TableSetupColumn("User name", ImGuiTableColumnFlags_WidthFixed, scale(120.0f), NetworkResourceColumnId_UserName); + ImGui::TableSetupColumn("Downloads", ImGuiTableColumnFlags_WidthFixed, 0.0f, NetworkResourceColumnId_NumDownloads); + ImGui::TableSetupColumn("Width", ImGuiTableColumnFlags_WidthFixed, 0.0f, NetworkResourceColumnId_Width); + ImGui::TableSetupColumn("Height", ImGuiTableColumnFlags_WidthFixed, 0.0f, NetworkResourceColumnId_Height); + ImGui::TableSetupColumn("Objects", ImGuiTableColumnFlags_WidthFixed, 0.0f, NetworkResourceColumnId_Particles); + ImGui::TableSetupColumn("File size", ImGuiTableColumnFlags_WidthFixed, 0.0f, NetworkResourceColumnId_FileSize); + ImGui::TableSetupColumn("Version", ImGuiTableColumnFlags_WidthFixed, 0.0f, NetworkResourceColumnId_Version); ImGui::TableSetupScrollFreeze(0, 1); ImGui::TableHeadersRow(); - //sort our data if sort specs have been changed! + //create table data if necessary if (ImGuiTableSortSpecs* sortSpecs = ImGui::TableGetSortSpecs()) { - if (sortSpecs->SpecsDirty || _scheduleSort) { - if (_filteredRemoteSimulationList.size() > 1) { - std::sort(_filteredRemoteSimulationList.begin(), _filteredRemoteSimulationList.end(), [&](auto const& left, auto const& right) { - return RemoteSimulationData::compare(&left, &right, sortSpecs) < 0; - }); - } + if (sortSpecs->SpecsDirty || _scheduleCreateSimulationTreeTOs) { + sortRawTOs(_simulations.rawTOs, sortSpecs); sortSpecs->SpecsDirty = false; + _scheduleCreateSimulationTreeTOs = false; + + _simulations.treeTOs = NetworkResourceService::createTreeTOs(_simulations.rawTOs, _simulations.collapsedFolderNames); } } ImGuiListClipper clipper; - clipper.Begin(_filteredRemoteSimulationList.size()); + clipper.Begin(_simulations.treeTOs.size()); while (clipper.Step()) for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; row++) { - - RemoteSimulationData* item = &_filteredRemoteSimulationList[row]; + auto treeTO = _simulations.treeTOs[row]; ImGui::PushID(row); ImGui::TableNextRow(0, scale(RowHeight)); - ImGui::TableNextColumn(); - processActionButtons(item); - ImGui::TableNextColumn(); - pushTextColor(*item); - AlienImGui::Text(item->timestamp); + + auto selected = _selectedResource == treeTO; + if (ImGui::Selectable( + "", + &selected, + ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowItemOverlap, + ImVec2(0, scale(RowHeight) - ImGui::GetStyle().FramePadding.y))) { + _selectedResource = selected ? treeTO : nullptr; + } + ImGui::SameLine(); + + pushTextColor(treeTO); + + processResourceNameField(treeTO, _simulations.collapsedFolderNames); ImGui::TableNextColumn(); - processShortenedText(item->userName); + processDescriptionField(treeTO); ImGui::TableNextColumn(); - processShortenedText(item->simName); + processReactionList(treeTO); ImGui::TableNextColumn(); - processShortenedText(item->description); + processTimestampField(treeTO); ImGui::TableNextColumn(); - processEmojiList(item); - + processUserNameField(treeTO); ImGui::TableNextColumn(); - AlienImGui::Text(std::to_string(item->numDownloads)); + processNumDownloadsField(treeTO); ImGui::TableNextColumn(); - AlienImGui::Text(std::to_string(item->width)); + processWidthField(treeTO); ImGui::TableNextColumn(); - AlienImGui::Text(std::to_string(item->height)); + processHeightField(treeTO); ImGui::TableNextColumn(); - AlienImGui::Text(StringHelper::format(item->particles / 1000) + " K"); + processNumParticlesField(treeTO); ImGui::TableNextColumn(); - AlienImGui::Text(StringHelper::format(item->contentSize / 1024) + " KB"); + processSizeField(treeTO, true); ImGui::TableNextColumn(); - AlienImGui::Text(item->version); + processVersionField(treeTO); + + popTextColor(); - ImGui::PopStyleColor(); ImGui::PopID(); } ImGui::EndTable(); @@ -366,93 +390,82 @@ void _BrowserWindow::processSimulationList() void _BrowserWindow::processGenomeList() { ImGui::PushID("GenomeList"); - _selectedDataType = DataType_Genome; - auto styleRepository = StyleRepository::getInstance(); + _selectedDataType = NetworkResourceType_Genome; + auto& styleRepository = StyleRepository::getInstance(); static ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Sortable | ImGuiTableFlags_SortMulti | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_ScrollY | ImGuiTableFlags_ScrollX; - if (ImGui::BeginTable("Browser", 10, flags, ImVec2(0, 0), 0.0f)) { - ImGui::TableSetupColumn( - "Actions", ImGuiTableColumnFlags_PreferSortDescending | ImGuiTableColumnFlags_WidthFixed, scale(90.0f), RemoteSimulationDataColumnId_Actions); + if (ImGui::BeginTable("Browser", 9, flags, ImVec2(0, 0), 0.0f)) { + ImGui::TableSetupColumn("Genome", ImGuiTableColumnFlags_WidthFixed, styleRepository.scale(210.0f), NetworkResourceColumnId_SimulationName); + ImGui::TableSetupColumn("Description", ImGuiTableColumnFlags_WidthFixed, styleRepository.scale(200.0f), NetworkResourceColumnId_Description); + ImGui::TableSetupColumn("Reactions", ImGuiTableColumnFlags_WidthFixed, styleRepository.scale(120.0f), NetworkResourceColumnId_Likes); ImGui::TableSetupColumn( "Timestamp", ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_PreferSortDescending, scale(135.0f), - RemoteSimulationDataColumnId_Timestamp); - ImGui::TableSetupColumn( - "User name", - ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed, - styleRepository.scale(120.0f), - RemoteSimulationDataColumnId_UserName); - ImGui::TableSetupColumn( - "Genome name", - ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed, - styleRepository.scale(160.0f), - RemoteSimulationDataColumnId_SimulationName); - ImGui::TableSetupColumn( - "Description", - ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed, - styleRepository.scale(120.0f), - RemoteSimulationDataColumnId_Description); - ImGui::TableSetupColumn( - "Reactions", - ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed, - styleRepository.scale(120.0f), - RemoteSimulationDataColumnId_Likes); - ImGui::TableSetupColumn( - "Downloads", ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed, 0.0f, RemoteSimulationDataColumnId_NumDownloads); - ImGui::TableSetupColumn("Cells", ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed, 0.0f, RemoteSimulationDataColumnId_Particles); - ImGui::TableSetupColumn("File size", ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed, 0.0f, RemoteSimulationDataColumnId_FileSize); - ImGui::TableSetupColumn("Version", ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed, 0.0f, RemoteSimulationDataColumnId_Version); + NetworkResourceColumnId_Timestamp); + ImGui::TableSetupColumn("User name", ImGuiTableColumnFlags_WidthFixed, styleRepository.scale(120.0f), NetworkResourceColumnId_UserName); + ImGui::TableSetupColumn("Downloads", ImGuiTableColumnFlags_WidthFixed, 0.0f, NetworkResourceColumnId_NumDownloads); + ImGui::TableSetupColumn("Cells", ImGuiTableColumnFlags_WidthFixed, 0.0f, NetworkResourceColumnId_Particles); + ImGui::TableSetupColumn("File size", ImGuiTableColumnFlags_WidthFixed, 0.0f, NetworkResourceColumnId_FileSize); + ImGui::TableSetupColumn("Version", ImGuiTableColumnFlags_WidthFixed, 0.0f, NetworkResourceColumnId_Version); ImGui::TableSetupScrollFreeze(0, 1); ImGui::TableHeadersRow(); - //sort our data if sort specs have been changed! + //create table data if necessary if (ImGuiTableSortSpecs* sortSpecs = ImGui::TableGetSortSpecs()) { - if (sortSpecs->SpecsDirty || _scheduleSort) { - if (_filteredRemoteGenomeList.size() > 1) { - std::sort(_filteredRemoteGenomeList.begin(), _filteredRemoteGenomeList.end(), [&](auto const& left, auto const& right) { - return RemoteSimulationData::compare(&left, &right, sortSpecs) < 0; - }); - } + if (sortSpecs->SpecsDirty || _scheduleCreateGenomeTreeTOs) { + sortRawTOs(_genomes.rawTOs, sortSpecs); sortSpecs->SpecsDirty = false; + _scheduleCreateGenomeTreeTOs = false; + + _genomes.treeTOs = NetworkResourceService::createTreeTOs(_genomes.rawTOs, _simulations.collapsedFolderNames); } } ImGuiListClipper clipper; - clipper.Begin(_filteredRemoteGenomeList.size()); + clipper.Begin(_genomes.treeTOs.size()); while (clipper.Step()) for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; row++) { - RemoteSimulationData* item = &_filteredRemoteGenomeList[row]; + auto& treeTO = _genomes.treeTOs[row]; ImGui::PushID(row); ImGui::TableNextRow(0, scale(RowHeight)); - ImGui::TableNextColumn(); - processActionButtons(item); - ImGui::TableNextColumn(); - pushTextColor(*item); - AlienImGui::Text(item->timestamp); + + auto selected = _selectedResource == treeTO; + if (ImGui::Selectable( + "", + &selected, + ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowItemOverlap, + ImVec2(0, scale(RowHeight) - ImGui::GetStyle().FramePadding.y))) { + _selectedResource = selected ? treeTO : nullptr; + } + ImGui::SameLine(); + + pushTextColor(treeTO); + + processResourceNameField(treeTO, _genomes.collapsedFolderNames); ImGui::TableNextColumn(); - processShortenedText(item->userName); + processDescriptionField(treeTO); ImGui::TableNextColumn(); - processShortenedText(item->simName); + processReactionList(treeTO); ImGui::TableNextColumn(); - processShortenedText(item->description); + processTimestampField(treeTO); ImGui::TableNextColumn(); - processEmojiList(item); - + processUserNameField(treeTO); ImGui::TableNextColumn(); - AlienImGui::Text(std::to_string(item->numDownloads)); + processNumDownloadsField(treeTO); ImGui::TableNextColumn(); - AlienImGui::Text(StringHelper::format(item->particles)); + processNumParticlesField(treeTO); ImGui::TableNextColumn(); - AlienImGui::Text(StringHelper::format(item->contentSize) + " Bytes"); + processSizeField(treeTO, false); ImGui::TableNextColumn(); - AlienImGui::Text(item->version); + processVersionField(treeTO); + + popTextColor(); - ImGui::PopStyleColor(); ImGui::PopID(); } ImGui::EndTable(); @@ -473,8 +486,10 @@ namespace void _BrowserWindow::processUserList() { - ImGui::PushID("UserTable"); - auto styleRepository = StyleRepository::getInstance(); + auto& networkService = NetworkService::getInstance(); + + ImGui::PushID("User list"); + auto& styleRepository = StyleRepository::getInstance(); static ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_ScrollY | ImGuiTableFlags_ScrollX; @@ -482,7 +497,7 @@ void _BrowserWindow::processUserList() AlienImGui::Group("Simulators"); if (ImGui::BeginTable("Browser", 5, flags, ImVec2(0, 0), 0.0f)) { ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_PreferSortDescending | ImGuiTableColumnFlags_WidthFixed, scale(90.0f)); - auto isLoggedIn = _networkController->getLoggedInUserName().has_value(); + auto isLoggedIn = networkService.getLoggedInUserName().has_value(); ImGui::TableSetupColumn( isLoggedIn ? "GPU model" : "GPU (visible if logged in)", ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed, @@ -495,16 +510,16 @@ void _BrowserWindow::processUserList() ImGui::TableHeadersRow(); ImGuiListClipper clipper; - clipper.Begin(_userList.size()); + clipper.Begin(_userTOs.size()); while (clipper.Step()) { for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; row++) { - auto item = &_userList[row]; + auto item = &_userTOs[row]; ImGui::PushID(row); ImGui::TableNextRow(0, scale(RowHeight)); ImGui::TableNextColumn(); - auto isBoldFont = isLoggedIn && *_networkController->getLoggedInUserName() == item->userName; + auto isBoldFont = isLoggedIn && *networkService.getLoggedInUserName() == item->userName; if (item->online) { AlienImGui::OnlineSymbol(); @@ -541,28 +556,29 @@ void _BrowserWindow::processUserList() void _BrowserWindow::processStatus() { - auto styleRepository = StyleRepository::getInstance(); + auto& styleRepository = StyleRepository::getInstance(); + auto& networkService = NetworkService::getInstance(); if (ImGui::BeginChild("##", ImVec2(0, styleRepository.scale(33.0f)), true)) { ImGui::PushStyleColor(ImGuiCol_Text, (ImVec4)Const::MonospaceColor); std::string statusText; statusText += std::string(" " ICON_FA_INFO_CIRCLE " "); - statusText += std::to_string(_numSimulations) + " simulations found"; + statusText += std::to_string(_simulations.numResources) + " simulations found"; statusText += std::string(" " ICON_FA_INFO_CIRCLE " "); - statusText += std::to_string(_numGenomes) + " genomes found"; + statusText += std::to_string(_genomes.numResources) + " genomes found"; statusText += std::string(" " ICON_FA_INFO_CIRCLE " "); - statusText += std::to_string(_userList.size()) + " simulators found"; + statusText += std::to_string(_userTOs.size()) + " simulators found"; statusText += std::string(" " ICON_FA_INFO_CIRCLE " "); - if (auto userName = _networkController->getLoggedInUserName()) { - statusText += "Logged in as " + *userName + " @ " + _networkController->getServerAddress();// + ": "; + if (auto userName = networkService.getLoggedInUserName()) { + statusText += "Logged in as " + *userName + " @ " + networkService.getServerAddress();// + ": "; } else { - statusText += "Not logged in to " + _networkController->getServerAddress();// + ": "; + statusText += "Not logged in to " + networkService.getServerAddress();// + ": "; } - if (!_networkController->getLoggedInUserName()) { + if (!networkService.getLoggedInUserName()) { statusText += std::string(" " ICON_FA_INFO_CIRCLE " "); statusText += "In order to share and upvote simulations you need to log in."; } @@ -576,14 +592,271 @@ void _BrowserWindow::processFilter() { ImGui::Spacing(); if (AlienImGui::ToggleButton(AlienImGui::ToggleButtonParameters().name("Community creations"), _showCommunityCreations)) { - calcFilteredSimulationAndGenomeLists(); + filterRawTOs(); + scheduleCreateTreeTOs(); } ImGui::SameLine(); if (AlienImGui::InputText(AlienImGui::InputTextParameters().name("Filter"), _filter)) { - calcFilteredSimulationAndGenomeLists(); + filterRawTOs(); + scheduleCreateTreeTOs(); + } +} + +void _BrowserWindow::processResourceNameField(NetworkResourceTreeTO const& treeTO, std::set>& collapsedFolderNames) +{ + if (treeTO->isLeaf()) { + auto& leaf = treeTO->getLeaf(); + + processFolderTreeSymbols(treeTO, _simulations.collapsedFolderNames); + processDownloadButton(leaf); + ImGui::SameLine(); + processShortenedText(leaf.leafName, true); + } else { + auto& folder = treeTO->getFolder(); + + processFolderTreeSymbols(treeTO, _simulations.collapsedFolderNames); + processShortenedText(treeTO->folderNames.back()); + ImGui::SameLine(); + ImGui::PushStyleColor(ImGuiCol_Text, (ImU32)Const::BrowserFolderPropertiesTextColor); + std::string resourceTypeString = [&] { + if (treeTO->type == NetworkResourceType_Simulation) { + return folder.numLeafs == 1 ? "sim" : "sims"; + } else { + return folder.numLeafs == 1 ? "genome" : "genomes"; + } + }(); + AlienImGui::Text("(" + std::to_string(folder.numLeafs) + " " + resourceTypeString + ")"); + ImGui::PopStyleColor(); + } +} + +void _BrowserWindow::processDescriptionField(NetworkResourceTreeTO const& treeTO) +{ + if (treeTO->isLeaf()) { + auto& leaf = treeTO->getLeaf(); + processShortenedText(leaf.rawTO->description); + } +} + +void _BrowserWindow::processReactionList(NetworkResourceTreeTO const& treeTO) +{ + if (treeTO->isLeaf()) { + auto& leaf = treeTO->getLeaf(); + ImGui::PushStyleColor(ImGuiCol_Text, (ImU32)Const::BrowserDownloadButtonTextColor); + auto isAddReaction = processActionButton(ICON_FA_PLUS); + ImGui::PopStyleColor(); + AlienImGui::Tooltip("Add a reaction"); + if (isAddReaction) { + _activateEmojiPopup = true; + _emojiPopupTO = treeTO; + } + + //calc remap which allows to show most frequent like type first + std::map remap; + std::set processedEmojiTypes; + + int index = 0; + while (processedEmojiTypes.size() < leaf.rawTO->numLikesByEmojiType.size()) { + int maxLikes = 0; + std::optional maxEmojiType; + for (auto const& [emojiType, numLikes] : leaf.rawTO->numLikesByEmojiType) { + if (!processedEmojiTypes.contains(emojiType) && numLikes > maxLikes) { + maxLikes = numLikes; + maxEmojiType = emojiType; + } + } + processedEmojiTypes.insert(*maxEmojiType); + remap.emplace(index, *maxEmojiType); + ++index; + } + + //show like types with count + int counter = 0; + std::optional toggleEmojiType; + for (auto const& emojiType : remap | std::views::values) { + auto numLikes = leaf.rawTO->numLikesByEmojiType.at(emojiType); + + ImGui::SameLine(); + AlienImGui::Text(std::to_string(numLikes)); + if (emojiType < _emojis.size()) { + ImGui::SameLine(); + auto const& emoji = _emojis.at(emojiType); + ImGui::SetCursorPosX(ImGui::GetCursorPosX() - scale(7.0f)); + ImGui::PushStyleColor(ImGuiCol_Button, static_cast(Const::ToolbarButtonBackgroundColor)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, static_cast(Const::ToolbarButtonHoveredColor)); + auto cursorPos = ImGui::GetCursorScreenPos(); + auto emojiWidth = scale(toFloat(emoji.width) / 2.5f); + auto emojiHeight = scale(toFloat(emoji.height) / 2.5f); + if (ImGui::ImageButton((void*)(intptr_t)emoji.textureId, {emojiWidth, emojiHeight}, ImVec2(0, 0), ImVec2(1, 1), 0)) { + toggleEmojiType = emojiType; + } + bool isLiked = _ownEmojiTypeBySimId.contains(leaf.rawTO->id) && _ownEmojiTypeBySimId.at(leaf.rawTO->id) == emojiType; + if (isLiked) { + ImGui::GetWindowDrawList()->AddRect( + ImVec2(cursorPos.x, cursorPos.y), + ImVec2(cursorPos.x + emojiWidth, cursorPos.y + emojiHeight), + (ImU32)ImColor::HSV(0, 0, 1, 0.5f), + 1.0f); + } + ImGui::PopStyleColor(2); + AlienImGui::Tooltip([=, this] { return getUserNamesToEmojiType(leaf.rawTO->id, emojiType); }, false); + } + + //separator except for last element + if (++counter < leaf.rawTO->numLikesByEmojiType.size()) { + ImGui::SetCursorPosX(ImGui::GetCursorPosX() - scale(4.0f)); + } + } + if (toggleEmojiType) { + onToggleLike(treeTO, *toggleEmojiType); + } + } else { + auto& folder = treeTO->getFolder(); + + auto pos = ImGui::GetCursorScreenPos(); + ImGui::SetCursorScreenPos({pos.x + scale(3.0f), pos.y}); + ImGui::PushStyleColor(ImGuiCol_Text, (ImU32)Const::BrowserFolderPropertiesTextColor); + AlienImGui::Text("(" + std::to_string(folder.numReactions) + ")"); + ImGui::PopStyleColor(); + } +} + +void _BrowserWindow::processTimestampField(NetworkResourceTreeTO const& treeTO) +{ + if (treeTO->isLeaf()) { + auto& leaf = treeTO->getLeaf(); + AlienImGui::Text(leaf.rawTO->timestamp); + } +} + +void _BrowserWindow::processUserNameField(NetworkResourceTreeTO const& treeTO) +{ + if (treeTO->isLeaf()) { + auto& leaf = treeTO->getLeaf(); + processShortenedText(leaf.rawTO->userName); + } +} + +void _BrowserWindow::processNumDownloadsField(NetworkResourceTreeTO const& treeTO) +{ + if (treeTO->isLeaf()) { + auto& leaf = treeTO->getLeaf(); + AlienImGui::Text(std::to_string(leaf.rawTO->numDownloads)); + } +} + +void _BrowserWindow::processWidthField(NetworkResourceTreeTO const& treeTO) +{ + if (treeTO->isLeaf()) { + auto& leaf = treeTO->getLeaf(); + AlienImGui::Text(std::to_string(leaf.rawTO->width)); + } +} + +void _BrowserWindow::processHeightField(NetworkResourceTreeTO const& treeTO) +{ + if (treeTO->isLeaf()) { + auto& leaf = treeTO->getLeaf(); + AlienImGui::Text(std::to_string(leaf.rawTO->height)); + } +} + +void _BrowserWindow::processNumParticlesField(NetworkResourceTreeTO const& treeTO) +{ + if (treeTO->isLeaf()) { + auto& leaf = treeTO->getLeaf(); + AlienImGui::Text(StringHelper::format(leaf.rawTO->particles / 1000) + " K"); + } +} + +void _BrowserWindow::processSizeField(NetworkResourceTreeTO const& treeTO, bool kbyte) +{ + if (treeTO->isLeaf()) { + auto& leaf = treeTO->getLeaf(); + if (kbyte) { + AlienImGui::Text(StringHelper::format(leaf.rawTO->contentSize / 1024) + " KB"); + } else { + AlienImGui::Text(StringHelper::format(leaf.rawTO->contentSize) + " Bytes"); + } } } +void _BrowserWindow::processVersionField(NetworkResourceTreeTO const& treeTO) +{ + if (treeTO->isLeaf()) { + auto& leaf = treeTO->getLeaf(); + AlienImGui::Text(leaf.rawTO->version); + } +} + +void _BrowserWindow::processFolderTreeSymbols(NetworkResourceTreeTO const& treeTO, std::set>& collapsedFolderNames) +{ + ImGui::PushStyleColor(ImGuiCol_Text, (ImU32)Const::BrowserFolderSymbolColor); + ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(0, 0, 0, 0)); + auto const& treeSymbols = treeTO->treeSymbols; + for (auto const& folderLine : treeSymbols) { + ImVec2 pos = ImGui::GetCursorScreenPos(); + ImGuiStyle& style = ImGui::GetStyle(); + switch (folderLine) { + case FolderTreeSymbols::Expanded: { + if (AlienImGui::Button(ICON_FA_MINUS_SQUARE, 20.0f)) { + collapsedFolderNames.insert(treeTO->folderNames); + scheduleCreateTreeTOs(); + } + } break; + case FolderTreeSymbols::Collapsed: { + if (AlienImGui::Button(ICON_FA_PLUS_SQUARE, 20.0f)) { + collapsedFolderNames.erase(treeTO->folderNames); + scheduleCreateTreeTOs(); + } + } break; + case FolderTreeSymbols::Continue: { + ImGui::GetWindowDrawList()->AddRectFilled( + ImVec2(pos.x + style.FramePadding.x + scale(6.0f), pos.y), + ImVec2(pos.x + style.FramePadding.x + scale(7.5f), pos.y + scale(RowHeight) + style.FramePadding.y), + Const::BrowserFolderLineColor); + ImGui::Dummy({scale(20.0f), 0}); + } break; + case FolderTreeSymbols::Branch: { + ImGui::GetWindowDrawList()->AddRectFilled( + ImVec2(pos.x + style.FramePadding.x + scale(6.0f), pos.y), + ImVec2(pos.x + style.FramePadding.x + scale(7.5f), pos.y + scale(RowHeight) + style.FramePadding.y), + Const::BrowserFolderLineColor); + ImGui::GetWindowDrawList()->AddRectFilled( + ImVec2(pos.x + style.FramePadding.x + scale(7.5f), pos.y + scale(RowHeight) / 2 - style.FramePadding.y), + ImVec2(pos.x + style.FramePadding.x + scale(20.0f), pos.y + scale(RowHeight) / 2 - style.FramePadding.y + scale(1.5f)), + Const::BrowserFolderLineColor); + ImGui::GetWindowDrawList()->AddRectFilled( + ImVec2(pos.x + style.FramePadding.x + scale(20.0f - 0.5f), pos.y + scale(RowHeight) / 2 - style.FramePadding.y - scale(0.5f)), + ImVec2(pos.x + style.FramePadding.x + scale(20.0f + 2.0f), pos.y + scale(RowHeight) / 2 - style.FramePadding.y + scale(2.0f)), + Const::BrowserFolderLineColor); + ImGui::Dummy({scale(20.0f), 0}); + } break; + case FolderTreeSymbols::End: { + ImGui::GetWindowDrawList()->AddRectFilled( + ImVec2(pos.x + style.FramePadding.x + scale(6.0f), pos.y), + ImVec2(pos.x + style.FramePadding.x + scale(7.5f), pos.y + scale(RowHeight) / 2 - style.FramePadding.y + scale(1.5f)), + Const::BrowserFolderLineColor); + ImGui::GetWindowDrawList()->AddRectFilled( + ImVec2(pos.x + style.FramePadding.x + scale(7.5f), pos.y + scale(RowHeight) / 2 - style.FramePadding.y), + ImVec2(pos.x + style.FramePadding.x + scale(20.0f), pos.y + scale(RowHeight) / 2 - style.FramePadding.y + scale(1.5f)), + Const::BrowserFolderLineColor); + ImGui::GetWindowDrawList()->AddRectFilled( + ImVec2(pos.x + style.FramePadding.x + scale(20.0f - 0.5f), pos.y + scale(RowHeight) / 2 - style.FramePadding.y - scale(0.5f)), + ImVec2(pos.x + style.FramePadding.x + scale(20.0f + 2.0f), pos.y + scale(RowHeight) / 2 - style.FramePadding.y + scale(2.0f)), + Const::BrowserFolderLineColor); + ImGui::Dummy({scale(20.0f), 0}); + } break; + case FolderTreeSymbols::None: { + ImGui::Dummy({scale(20.0f), 0}); + } break; + default: { + } break; + } + ImGui::SameLine(); + } + ImGui::PopStyleColor(2); +} void _BrowserWindow::processEmojiWindow() { @@ -596,7 +869,7 @@ void _BrowserWindow::processEmojiWindow() ImGui::Spacing(); ImGui::Spacing(); if (_showAllEmojis) { - if (ImGui::BeginChild("##emojichild", ImVec2(scale(335), scale(300)), false)) { + if (ImGui::BeginChild("##reactionchild", ImVec2(scale(335), scale(300)), false)) { int offset = 0; for (int i = 0; i < NumEmojiBlocks; ++i) { for (int j = 0; j < NumEmojisPerBlock[i]; ++j) { @@ -611,7 +884,7 @@ void _BrowserWindow::processEmojiWindow() } ImGui::EndChild(); } else { - if (ImGui::BeginChild("##emojichild", ImVec2(scale(335), scale(90)), false)) { + if (ImGui::BeginChild("##reactionchild", ImVec2(scale(335), scale(90)), false)) { for (int i = 0; i < NumEmojisPerRow; ++i) { if (i % NumEmojisPerRow != 0) { ImGui::SameLine(); @@ -640,14 +913,14 @@ void _BrowserWindow::processEmojiButton(int emojiType) auto cursorPos = ImGui::GetCursorScreenPos(); auto emojiWidth = scale(toFloat(emoji.width)); auto emojiHeight = scale(toFloat(emoji.height)); - auto const& sim = _simOfEmojiPopup; + auto leaf = _emojiPopupTO->getLeaf(); if (ImGui::ImageButton((void*)(intptr_t)emoji.textureId, {emojiWidth, emojiHeight}, {0, 0}, {1.0f, 1.0f})) { - onToggleLike(sim, toInt(emojiType)); + onToggleLike(_emojiPopupTO, toInt(emojiType)); ImGui::CloseCurrentPopup(); } ImGui::PopStyleColor(2); - bool isLiked = _ownEmojiTypeBySimId.contains(sim->id) && _ownEmojiTypeBySimId.at(sim->id) == emojiType; + bool isLiked = _ownEmojiTypeBySimId.contains(leaf.rawTO->id) && _ownEmojiTypeBySimId.at(leaf.rawTO->id) == emojiType; if (isLiked) { ImDrawList* drawList = ImGui::GetWindowDrawList(); auto& style = ImGui::GetStyle(); @@ -659,109 +932,15 @@ void _BrowserWindow::processEmojiButton(int emojiType) } } -void _BrowserWindow::processEmojiList(RemoteSimulationData* sim) -{ - //calc remap which allows to show most frequent like type first - std::map remap; - std::set processedEmojiTypes; - - int index = 0; - while (processedEmojiTypes.size() < sim->numLikesByEmojiType.size()) { - int maxLikes = 0; - std::optional maxEmojiType; - for (auto const& [emojiType, numLikes] : sim->numLikesByEmojiType) { - if (!processedEmojiTypes.contains(emojiType) && numLikes > maxLikes) { - maxLikes = numLikes; - maxEmojiType = emojiType; - } - } - processedEmojiTypes.insert(*maxEmojiType); - remap.emplace(index, *maxEmojiType); - ++index; - } - - //show like types with count - int counter = 0; - std::optional toggleEmojiType; - for (auto const& emojiType : remap | std::views::values) { - auto numLikes = sim->numLikesByEmojiType.at(emojiType); - - AlienImGui::Text(std::to_string(numLikes)); - ImGui::SameLine(); - if (emojiType < _emojis.size()) { - auto const& emoji = _emojis.at(emojiType); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() - scale(7.0f)); - ImGui::SetCursorPosY(ImGui::GetCursorPosY() + scale(1.0f)); - ImGui::PushStyleColor(ImGuiCol_Button, static_cast(Const::ToolbarButtonBackgroundColor)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, static_cast(Const::ToolbarButtonHoveredColor)); - auto cursorPos = ImGui::GetCursorScreenPos(); - auto emojiWidth = scale(toFloat(emoji.width) / 2.5f); - auto emojiHeight = scale(toFloat(emoji.height) / 2.5f); - if (ImGui::ImageButton((void*)(intptr_t)emoji.textureId, {emojiWidth, emojiHeight}, - ImVec2(0, 0), - ImVec2(1, 1), - 0)) { - toggleEmojiType = emojiType; - } - bool isLiked = _ownEmojiTypeBySimId.contains(sim->id) && _ownEmojiTypeBySimId.at(sim->id) == emojiType; - if (isLiked) { - ImDrawList* drawList = ImGui::GetWindowDrawList(); - drawList->AddRect( - ImVec2(cursorPos.x, cursorPos.y), ImVec2(cursorPos.x + emojiWidth, cursorPos.y + emojiHeight), (ImU32)ImColor::HSV(0, 0, 1, 0.5f), 1.0f); - } - ImGui::PopStyleColor(2); - AlienImGui::Tooltip([=, this] { return getUserNamesToEmojiType(sim->id, emojiType); }, false); - } - - //separator except for last element - if (++counter < sim->numLikesByEmojiType.size()) { - ImGui::SameLine(); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() - scale(4.0f)); - } - } - if (toggleEmojiType) { - onToggleLike(sim, *toggleEmojiType); - } -} - -void _BrowserWindow::processActionButtons(RemoteSimulationData* simData) +void _BrowserWindow::processDownloadButton(BrowserLeaf const& leaf) { - //like button - auto liked = isLiked(simData->id); - if (liked) { - ImGui::PushStyleColor(ImGuiCol_Text, (ImU32)Const::LikeButtonTextColor); - } else { - ImGui::PushStyleColor(ImGuiCol_Text, (ImU32)Const::NoLikeButtonTextColor); - } - auto likeButtonResult = processActionButton(ICON_FA_SMILE); - ImGui::PopStyleColor(); - if (likeButtonResult) { - _activateEmojiPopup = true; - _simOfEmojiPopup = simData; - } - AlienImGui::Tooltip("Choose a reaction"); - ImGui::SameLine(); - - //download button - ImGui::PushStyleColor(ImGuiCol_Text, (ImU32)Const::DownloadButtonTextColor); + ImGui::PushStyleColor(ImGuiCol_Text, (ImU32)Const::BrowserDownloadButtonTextColor); auto downloadButtonResult = processActionButton(ICON_FA_DOWNLOAD); ImGui::PopStyleColor(); if (downloadButtonResult) { - onDownloadItem(simData); + onDownloadItem(leaf); } AlienImGui::Tooltip("Download"); - ImGui::SameLine(); - - //delete button - if (simData->userName == _networkController->getLoggedInUserName().value_or("")) { - ImGui::PushStyleColor(ImGuiCol_Text, (ImU32)Const::DeleteButtonTextColor); - auto deleteButtonResult = processActionButton(ICON_FA_TRASH); - ImGui::PopStyleColor(); - if (deleteButtonResult) { - onDeleteItem(simData); - } - AlienImGui::Tooltip("Delete"); - } } namespace @@ -779,7 +958,7 @@ void _BrowserWindow::processShortenedText(std::string const& text, bool bold) { if (substrings.empty()) { return; } - auto styleRepository = StyleRepository::getInstance(); + auto& styleRepository = StyleRepository::getInstance(); auto textSize = ImGui::CalcTextSize(substrings.at(0).c_str()); auto needDetailButton = textSize.x > ImGui::GetContentRegionAvailWidth() || substrings.size() > 1; auto cursorPos = ImGui::GetCursorPosX() + ImGui::GetContentRegionAvailWidth() - styleRepository.scale(15.0f); @@ -826,29 +1005,57 @@ void _BrowserWindow::processActivated() onRefresh(); } -void _BrowserWindow::sortSimulationList() +void _BrowserWindow::scheduleCreateTreeTOs() +{ + _scheduleCreateSimulationTreeTOs = true; + _scheduleCreateGenomeTreeTOs = true; +} + +void _BrowserWindow::sortRawTOs(std::vector& tos, ImGuiTableSortSpecs* sortSpecs) { - _scheduleSort = true; + if (tos.size() > 1) { + std::sort(tos.begin(), tos.end(), [&](auto const& left, auto const& right) { + return _NetworkResourceRawTO::compare(left, right, sortSpecs) < 0; + }); + } } void _BrowserWindow::sortUserList() { - std::sort(_userList.begin(), _userList.end(), [&](auto const& left, auto const& right) { return UserData::compareOnlineAndTimestamp(left, right) > 0; }); + std::sort(_userTOs.begin(), _userTOs.end(), [&](auto const& left, auto const& right) { return UserTO::compareOnlineAndTimestamp(left, right) > 0; }); } -void _BrowserWindow::onDownloadItem(RemoteSimulationData* sim) +void _BrowserWindow::filterRawTOs() +{ + _simulations.rawTOs.clear(); + _simulations.rawTOs.reserve(_unfilteredRawTOs.size()); + _genomes.rawTOs.clear(); + _genomes.rawTOs.reserve(_unfilteredRawTOs.size()); + for (auto const& to : _unfilteredRawTOs) { + if (to->matchWithFilter(_filter) && _showCommunityCreations != to->fromRelease) { + if (to->type == NetworkResourceType_Simulation) { + _simulations.rawTOs.emplace_back(to); + } else { + _genomes.rawTOs.emplace_back(to); + } + } + } +} + +void _BrowserWindow::onDownloadItem(BrowserLeaf const& leaf) { printOverlayMessage("Downloading ..."); delayedExecution([=, this] { - std::string dataTypeString = _selectedDataType == DataType_Simulation ? "simulation" : "genome"; + auto& networkService = NetworkService::getInstance(); + std::string dataTypeString = _selectedDataType == NetworkResourceType_Simulation ? "simulation" : "genome"; SerializedSimulation serializedSim; - if (!_networkController->downloadSimulation(serializedSim.mainData, serializedSim.auxiliaryData, serializedSim.statistics, sim->id)) { + if (!networkService.downloadSimulation(serializedSim.mainData, serializedSim.auxiliaryData, serializedSim.statistics, leaf.rawTO->id)) { MessageDialog::getInstance().information("Error", "Failed to download " + dataTypeString + "."); return; } - if (_selectedDataType == DataType_Simulation) { + if (_selectedDataType == NetworkResourceType_Simulation) { DeserializedSimulation deserializedSim; if (!SerializerService::deserializeSimulationFromStrings(deserializedSim, serializedSim)) { MessageDialog::getInstance().information("Error", "Failed to load simulation. Your program version may not match."); @@ -889,7 +1096,7 @@ void _BrowserWindow::onDownloadItem(RemoteSimulationData* sim) _editorController->setOn(true); _editorController->getGenomeEditorWindow()->openTab(GenomeDescriptionService::convertBytesToDescription(genome)); } - if (VersionChecker::isVersionNewer(sim->version)) { + if (VersionChecker::isVersionNewer(leaf.rawTO->version)) { MessageDialog::getInstance().information( "Warning", "The download was successful but the " + dataTypeString +" was generated using a more recent\n" @@ -899,13 +1106,14 @@ void _BrowserWindow::onDownloadItem(RemoteSimulationData* sim) }); } -void _BrowserWindow::onDeleteItem(RemoteSimulationData* sim) +void _BrowserWindow::onDeleteItem(BrowserLeaf const& leaf) { - MessageDialog::getInstance().yesNo("Delete item", "Do you really want to delete the selected item?", [sim, this]() { + MessageDialog::getInstance().yesNo("Delete item", "Do you really want to delete the selected item?", [leaf, this]() { printOverlayMessage("Deleting ..."); - delayedExecution([remoteData = sim, this] { - if (!_networkController->deleteSimulation(remoteData->id)) { + delayedExecution([leafCopy = leaf, this] { + auto& networkService = NetworkService::getInstance(); + if (!networkService.deleteSimulation(leafCopy.rawTO->id)) { MessageDialog::getInstance().information("Error", "Failed to delete item. Please try again later."); return; } @@ -914,36 +1122,38 @@ void _BrowserWindow::onDeleteItem(RemoteSimulationData* sim) }); } -void _BrowserWindow::onToggleLike(RemoteSimulationData* sim, int emojiType) +void _BrowserWindow::onToggleLike(NetworkResourceTreeTO const& to, int emojiType) { - if (_networkController->getLoggedInUserName()) { + CHECK(to->isLeaf()); + auto& leaf = to->getLeaf(); + auto& networkService = NetworkService::getInstance(); + if (networkService.getLoggedInUserName()) { //remove existing like - auto findResult = _ownEmojiTypeBySimId.find(sim->id); + auto findResult = _ownEmojiTypeBySimId.find(leaf.rawTO->id); auto onlyRemoveLike = false; if (findResult != _ownEmojiTypeBySimId.end()) { auto origEmojiType = findResult->second; - if (--sim->numLikesByEmojiType[origEmojiType] == 0) { - sim->numLikesByEmojiType.erase(origEmojiType); + if (--leaf.rawTO->numLikesByEmojiType[origEmojiType] == 0) { + leaf.rawTO->numLikesByEmojiType.erase(origEmojiType); } _ownEmojiTypeBySimId.erase(findResult); - _userNamesByEmojiTypeBySimIdCache.erase(std::make_pair(sim->id, origEmojiType)); //invalidate cache entry + _userNamesByEmojiTypeBySimIdCache.erase(std::make_pair(leaf.rawTO->id, origEmojiType)); //invalidate cache entry onlyRemoveLike = origEmojiType == emojiType; //remove like if same like icon has been clicked } //create new like if (!onlyRemoveLike) { - _ownEmojiTypeBySimId[sim->id] = emojiType; - if (sim->numLikesByEmojiType.contains(emojiType)) { - ++sim->numLikesByEmojiType[emojiType]; + _ownEmojiTypeBySimId[leaf.rawTO->id] = emojiType; + if (leaf.rawTO->numLikesByEmojiType.contains(emojiType)) { + ++leaf.rawTO->numLikesByEmojiType[emojiType]; } else { - sim->numLikesByEmojiType[emojiType] = 1; + leaf.rawTO->numLikesByEmojiType[emojiType] = 1; } } - _userNamesByEmojiTypeBySimIdCache.erase(std::make_pair(sim->id, emojiType)); //invalidate cache entry - _networkController->toggleLikeSimulation(sim->id, emojiType); - sortSimulationList(); + _userNamesByEmojiTypeBySimIdCache.erase(std::make_pair(leaf.rawTO->id, emojiType)); //invalidate cache entry + networkService.toggleLikeSimulation(leaf.rawTO->id, emojiType); } else { _loginDialog.lock()->open(); } @@ -963,43 +1173,38 @@ bool _BrowserWindow::isLiked(std::string const& simId) std::string _BrowserWindow::getUserNamesToEmojiType(std::string const& simId, int emojiType) { + auto& networkService = NetworkService::getInstance(); + std::set userNames; auto findResult = _userNamesByEmojiTypeBySimIdCache.find(std::make_pair(simId, emojiType)); if (findResult != _userNamesByEmojiTypeBySimIdCache.end()) { userNames = findResult->second; } else { - _networkController->getUserNamesForSimulationAndEmojiType(userNames, simId, emojiType); + networkService.getUserNamesForSimulationAndEmojiType(userNames, simId, emojiType); _userNamesByEmojiTypeBySimIdCache.emplace(std::make_pair(simId, emojiType), userNames); } return boost::algorithm::join(userNames, ", "); } -void _BrowserWindow::pushTextColor(RemoteSimulationData const& entry) +void _BrowserWindow::pushTextColor(NetworkResourceTreeTO const& to) { - if (VersionChecker::isVersionOutdated(entry.version)) { - ImGui::PushStyleColor(ImGuiCol_Text, (ImVec4)Const::VersionOutdatedColor); - } else if (VersionChecker::isVersionNewer(entry.version)) { - ImGui::PushStyleColor(ImGuiCol_Text, (ImVec4)Const::VersionNewerColor); + if (to->isLeaf()) { + auto const& leaf = to->getLeaf(); + if (VersionChecker::isVersionOutdated(leaf.rawTO->version)) { + ImGui::PushStyleColor(ImGuiCol_Text, (ImVec4)Const::BrowserVersionOutdatedTextColor); + } else if (VersionChecker::isVersionNewer(leaf.rawTO->version)) { + ImGui::PushStyleColor(ImGuiCol_Text, (ImVec4)Const::BrowserVersionNewerTextColor); + } else { + ImGui::PushStyleColor(ImGuiCol_Text, (ImVec4)Const::BrowserVersionOkTextColor); + } } else { - ImGui::PushStyleColor(ImGuiCol_Text, (ImVec4)Const::VersionOkColor); + ImGui::PushStyleColor(ImGuiCol_Text, (ImVec4)Const::BrowserFolderTextColor); } } -void _BrowserWindow::calcFilteredSimulationAndGenomeLists() +void _BrowserWindow::popTextColor() { - _filteredRemoteSimulationList.clear(); - _filteredRemoteSimulationList.reserve(_rawRemoteDataList.size()); - _filteredRemoteGenomeList.clear(); - _filteredRemoteGenomeList.reserve(_filteredRemoteGenomeList.size()); - for (auto const& simData : _rawRemoteDataList) { - if (simData.matchWithFilter(_filter) &&_showCommunityCreations != simData.fromRelease) { - if (simData.type == RemoteDataType_Simulation) { - _filteredRemoteSimulationList.emplace_back(simData); - } else { - _filteredRemoteGenomeList.emplace_back(simData); - } - } - } + ImGui::PopStyleColor(); } diff --git a/source/Gui/BrowserWindow.h b/source/Gui/BrowserWindow.h index 322c32d46..2ffb95e23 100644 --- a/source/Gui/BrowserWindow.h +++ b/source/Gui/BrowserWindow.h @@ -4,10 +4,11 @@ #include "Base/Hashes.h" #include "EngineInterface/Definitions.h" +#include "Network/NetworkResourceTreeTO.h" +#include "Network/NetworkResourceRawTO.h" +#include "Network/UserTO.h" #include "AlienWindow.h" -#include "RemoteSimulationData.h" -#include "UserData.h" #include "Definitions.h" class _BrowserWindow : public _AlienWindow @@ -15,7 +16,6 @@ class _BrowserWindow : public _AlienWindow public: _BrowserWindow( SimulationController const& simController, - NetworkController const& networkController, StatisticsWindow const& statisticsWindow, Viewport const& viewport, TemporalControlWindow const& temporalControlWindow, @@ -27,6 +27,14 @@ class _BrowserWindow : public _AlienWindow void onRefresh(); private: + struct ResourceData + { + int numResources = 0; + std::vector rawTOs; + std::vector treeTOs; + std::set> collapsedFolderNames; + }; + void refreshIntern(bool withRetry); void processIntern() override; @@ -40,11 +48,23 @@ class _BrowserWindow : public _AlienWindow void processFilter(); void processToolbar(); + void processResourceNameField(NetworkResourceTreeTO const& treeTO, std::set>& collapsedFolderNames); + void processDescriptionField(NetworkResourceTreeTO const& treeTO); + void processReactionList(NetworkResourceTreeTO const& treeTO); + void processTimestampField(NetworkResourceTreeTO const& treeTO); + void processUserNameField(NetworkResourceTreeTO const& treeTO); + void processNumDownloadsField(NetworkResourceTreeTO const& treeTO); + void processWidthField(NetworkResourceTreeTO const& treeTO); + void processHeightField(NetworkResourceTreeTO const& treeTO); + void processNumParticlesField(NetworkResourceTreeTO const& treeTO); + void processSizeField(NetworkResourceTreeTO const& treeTO, bool kbyte); + void processVersionField(NetworkResourceTreeTO const& treeTO); + + void processFolderTreeSymbols(NetworkResourceTreeTO const& treeTO, std::set>& collapsedFolderNames); void processEmojiWindow(); void processEmojiButton(int emojiType); - void processEmojiList(RemoteSimulationData* sim); - void processActionButtons(RemoteSimulationData* simData); + void processDownloadButton(BrowserLeaf const& leaf); void processShortenedText(std::string const& text, bool bold = false); bool processActionButton(std::string const& text); @@ -52,23 +72,29 @@ class _BrowserWindow : public _AlienWindow void processActivated() override; - void sortSimulationList(); + void scheduleCreateTreeTOs(); + + void sortRawTOs(std::vector& tos, ImGuiTableSortSpecs* sortSpecs); void sortUserList(); - void onDownloadItem(RemoteSimulationData* sim); - void onDeleteItem(RemoteSimulationData* sim); - void onToggleLike(RemoteSimulationData* sim, int emojiType); + void filterRawTOs(); + + void onDownloadItem(BrowserLeaf const& leaf); + void onDeleteItem(BrowserLeaf const& leaf); + void onToggleLike(NetworkResourceTreeTO const& to, int emojiType); void openWeblink(std::string const& link); bool isLiked(std::string const& simId); std::string getUserNamesToEmojiType(std::string const& simId, int emojiType); - void pushTextColor(RemoteSimulationData const& entry); - void calcFilteredSimulationAndGenomeLists(); + void pushTextColor(NetworkResourceTreeTO const& to); + void popTextColor(); - DataType _selectedDataType = DataType_Simulation; + NetworkResourceType _selectedDataType = NetworkResourceType_Simulation; bool _scheduleRefresh = false; - bool _scheduleSort = false; + bool _scheduleCreateSimulationTreeTOs = false; + bool _scheduleCreateGenomeTreeTOs = false; + std::string _filter; bool _showCommunityCreations = false; float _userTableWidth = 0; @@ -76,24 +102,22 @@ class _BrowserWindow : public _AlienWindow std::unordered_map _ownEmojiTypeBySimId; std::unordered_map, std::set> _userNamesByEmojiTypeBySimIdCache; - int _numSimulations = 0; - int _numGenomes = 0; - std::vector _rawRemoteDataList; - std::vector _filteredRemoteSimulationList; - std::vector _filteredRemoteGenomeList; + std::vector _unfilteredRawTOs; + NetworkResourceTreeTO _selectedResource; + ResourceData _genomes; + ResourceData _simulations; - std::vector _userList; + std::vector _userTOs; std::vector _emojis; bool _activateEmojiPopup = false; bool _showAllEmojis = false; - RemoteSimulationData* _simOfEmojiPopup = nullptr; + NetworkResourceTreeTO _emojiPopupTO; std::optional _lastRefreshTime; SimulationController _simController; - NetworkController _networkController; StatisticsWindow _statisticsWindow; Viewport _viewport; TemporalControlWindow _temporalControlWindow; diff --git a/source/Gui/CMakeLists.txt b/source/Gui/CMakeLists.txt index e0ebc0f31..5ac073049 100644 --- a/source/Gui/CMakeLists.txt +++ b/source/Gui/CMakeLists.txt @@ -67,10 +67,6 @@ PUBLIC ModeController.h MultiplierWindow.cpp MultiplierWindow.h - NetworkController.cpp - NetworkController.h - NetworkDataParser.cpp - NetworkDataParser.h NetworkSettingsDialog.cpp NetworkSettingsDialog.h NewSimulationDialog.cpp @@ -87,8 +83,6 @@ PUBLIC PatternEditorWindow.h RadiationSourcesWindow.cpp RadiationSourcesWindow.h - RemoteSimulationData.cpp - RemoteSimulationData.h ResetPasswordDialog.cpp ResetPasswordDialog.h ResizeWorldDialog.cpp @@ -121,18 +115,18 @@ PUBLIC UiController.h UploadSimulationDialog.cpp UploadSimulationDialog.h - UserData.h Viewport.cpp Viewport.h WindowController.cpp WindowController.h) -target_link_libraries(alien alien_base_lib) -target_link_libraries(alien alien_engine_gpu_kernels_lib) -target_link_libraries(alien alien_engine_impl_lib) -target_link_libraries(alien alien_engine_interface_lib) -target_link_libraries(alien im_file_dialog) +target_link_libraries(alien Base) +target_link_libraries(alien EngineGpuKernels) +target_link_libraries(alien EngineImpl) +target_link_libraries(alien EngineInterface) +target_link_libraries(alien Network) +target_link_libraries(alien im_file_dialog) target_link_libraries(alien CUDA::cudart_static) target_link_libraries(alien CUDA::cuda_driver) target_link_libraries(alien Boost::boost) diff --git a/source/Gui/CreateUserDialog.cpp b/source/Gui/CreateUserDialog.cpp index 01e3431e2..ecaf02416 100644 --- a/source/Gui/CreateUserDialog.cpp +++ b/source/Gui/CreateUserDialog.cpp @@ -3,14 +3,14 @@ #include #include +#include "Network/NetworkService.h" + #include "AlienImGui.h" #include "MessageDialog.h" -#include "NetworkController.h" #include "ActivateUserDialog.h" -_CreateUserDialog::_CreateUserDialog(ActivateUserDialog const& activateUserDialog, NetworkController const& networkController) +_CreateUserDialog::_CreateUserDialog(ActivateUserDialog const& activateUserDialog) : _AlienDialog("Create user") - , _networkController(networkController) , _activateUserDialog(activateUserDialog) { } @@ -61,7 +61,9 @@ void _CreateUserDialog::processIntern() void _CreateUserDialog::onCreateUser() { - if (_networkController->createUser(_userName, _password, _email)) { + auto& networkService = NetworkService::getInstance(); + + if (networkService.createUser(_userName, _password, _email)) { _activateUserDialog->open(_userName, _password, _userInfo); } else { MessageDialog::getInstance().information( diff --git a/source/Gui/CreateUserDialog.h b/source/Gui/CreateUserDialog.h index 4cec86eb6..5ed7a18fb 100644 --- a/source/Gui/CreateUserDialog.h +++ b/source/Gui/CreateUserDialog.h @@ -1,13 +1,14 @@ #pragma once +#include "Network/NetworkService.h" + #include "AlienDialog.h" #include "Definitions.h" -#include "NetworkController.h" class _CreateUserDialog : public _AlienDialog { public: - _CreateUserDialog(ActivateUserDialog const& activateUserDialog, NetworkController const& networkController); + _CreateUserDialog(ActivateUserDialog const& activateUserDialog); ~_CreateUserDialog(); void open(std::string const& userName, std::string const& password, UserInfo const& userInfo); @@ -16,7 +17,6 @@ class _CreateUserDialog : public _AlienDialog private: void processIntern(); - NetworkController _networkController; ActivateUserDialog _activateUserDialog; std::string _userName; diff --git a/source/Gui/Definitions.h b/source/Gui/Definitions.h index 7796ab88a..031473364 100644 --- a/source/Gui/Definitions.h +++ b/source/Gui/Definitions.h @@ -115,9 +115,6 @@ using BrowserWindow = std::shared_ptr<_BrowserWindow>; class _ShaderWindow; using ShaderWindow = std::shared_ptr<_ShaderWindow>; -class _NetworkController; -using NetworkController = std::shared_ptr<_NetworkController>; - class _LoginDialog; using LoginDialog = std::shared_ptr<_LoginDialog>; using LoginDialogWeakPtr = std::weak_ptr<_LoginDialog>; @@ -169,11 +166,4 @@ struct TextureData unsigned int textureId; int width; int height; -}; - -using DataType = int; -enum DataType_ -{ - DataType_Simulation, - DataType_Genome -}; +}; \ No newline at end of file diff --git a/source/Gui/DeleteUserDialog.cpp b/source/Gui/DeleteUserDialog.cpp index 62f431b51..b733136e3 100644 --- a/source/Gui/DeleteUserDialog.cpp +++ b/source/Gui/DeleteUserDialog.cpp @@ -2,23 +2,25 @@ #include +#include "Network/NetworkService.h" + #include "AlienImGui.h" #include "BrowserWindow.h" #include "CreateUserDialog.h" #include "MessageDialog.h" -#include "NetworkController.h" -_DeleteUserDialog::_DeleteUserDialog(BrowserWindow const& browserWindow, NetworkController const& networkController) +_DeleteUserDialog::_DeleteUserDialog(BrowserWindow const& browserWindow) : _AlienDialog("Delete user") , _browserWindow(browserWindow) - , _networkController(networkController) { } void _DeleteUserDialog::processIntern() { + auto& networkService = NetworkService::getInstance(); + AlienImGui::Text( - "Warning: All the data of the user '" + *_networkController->getLoggedInUserName() + "Warning: All the data of the user '" + *networkService.getLoggedInUserName() + "' will be deleted on the server side.\nThese include the likes, the simulations and the account data."); AlienImGui::Separator(); @@ -28,7 +30,7 @@ void _DeleteUserDialog::processIntern() ImGui::BeginDisabled(_reenteredPassword.empty()); if (AlienImGui::Button("Delete")) { close(); - if (_reenteredPassword == *_networkController->getPassword()) { + if (_reenteredPassword == *networkService.getPassword()) { onDelete(); } else { MessageDialog::getInstance().information("Error", "The password does not match."); @@ -47,8 +49,9 @@ void _DeleteUserDialog::processIntern() void _DeleteUserDialog::onDelete() { - auto userName = *_networkController->getLoggedInUserName(); - if (_networkController->deleteUser()) { + auto& networkService = NetworkService::getInstance(); + auto userName = *networkService.getLoggedInUserName(); + if (networkService.deleteUser()) { _browserWindow->onRefresh(); MessageDialog::getInstance().information("Information", "The user '" + userName + "' has been deleted.\nYou are logged out."); } else { diff --git a/source/Gui/DeleteUserDialog.h b/source/Gui/DeleteUserDialog.h index 27ec0093e..de93b8b6f 100644 --- a/source/Gui/DeleteUserDialog.h +++ b/source/Gui/DeleteUserDialog.h @@ -1,19 +1,20 @@ #pragma once +#include "Network/Definitions.h" + #include "AlienDialog.h" #include "Definitions.h" class _DeleteUserDialog : public _AlienDialog { public: - _DeleteUserDialog(BrowserWindow const& browserWindow, NetworkController const& networkController); + _DeleteUserDialog(BrowserWindow const& browserWindow); private: void processIntern(); void onDelete(); BrowserWindow _browserWindow; - NetworkController _networkController; std::string _reenteredPassword; }; diff --git a/source/Gui/GenomeEditorWindow.cpp b/source/Gui/GenomeEditorWindow.cpp index abda9b8b5..81cb78e78 100644 --- a/source/Gui/GenomeEditorWindow.cpp +++ b/source/Gui/GenomeEditorWindow.cpp @@ -138,7 +138,7 @@ void _GenomeEditorWindow::processToolbar() AlienImGui::Tooltip("Save genome to file"); ImGui::SameLine(); - if (AlienImGui::ToolbarButton(ICON_FA_SHARE_ALT)) { + if (AlienImGui::ToolbarButton(ICON_FA_UPLOAD)) { onUploadGenome(); } AlienImGui::Tooltip("Share your genome with other users:\nYour current genome will be uploaded to the server and made visible in the browser."); @@ -831,7 +831,7 @@ void _GenomeEditorWindow::onSaveGenome() void _GenomeEditorWindow::onUploadGenome() { - _uploadSimulationDialog.lock()->open(DataType_Genome); + _uploadSimulationDialog.lock()->open(NetworkResourceType_Genome); } void _GenomeEditorWindow::onAddNode() diff --git a/source/Gui/LogWindow.cpp b/source/Gui/LogWindow.cpp index f6ef87248..cef034a53 100644 --- a/source/Gui/LogWindow.cpp +++ b/source/Gui/LogWindow.cpp @@ -24,7 +24,7 @@ _LogWindow::~_LogWindow() void _LogWindow::processIntern() { - auto styleRepository = StyleRepository::getInstance(); + auto& styleRepository = StyleRepository::getInstance(); if (ImGui::BeginChild( "##", ImVec2(0, ImGui::GetContentRegionAvail().y - styleRepository.scale(40.0f)), true, ImGuiWindowFlags_HorizontalScrollbar)) { ImGui::PushFont(StyleRepository::getInstance().getMonospaceMediumFont()); diff --git a/source/Gui/LoginDialog.cpp b/source/Gui/LoginDialog.cpp index ef226ab95..cd9505c5e 100644 --- a/source/Gui/LoginDialog.cpp +++ b/source/Gui/LoginDialog.cpp @@ -4,9 +4,9 @@ #include "Base/GlobalSettings.h" #include "EngineInterface/SimulationController.h" +#include "Network/NetworkService.h" #include "AlienImGui.h" -#include "NetworkController.h" #include "MessageDialog.h" #include "CreateUserDialog.h" #include "BrowserWindow.h" @@ -20,17 +20,16 @@ _LoginDialog::_LoginDialog( BrowserWindow const& browserWindow, CreateUserDialog const& createUserDialog, ActivateUserDialog const& activateUserDialog, - ResetPasswordDialog const& resetPasswordDialog, - NetworkController const& networkController) + ResetPasswordDialog const& resetPasswordDialog) : _AlienDialog("Login") , _simController(simController) , _browserWindow(browserWindow) , _createUserDialog(createUserDialog) , _activateUserDialog(activateUserDialog) - , _networkController(networkController) , _resetPasswordDialog(resetPasswordDialog) { + auto& networkService = NetworkService::getInstance(); auto& settings = GlobalSettings::getInstance(); _remember = settings.getBoolState("dialogs.login.remember", _remember); _shareGpuInfo = settings.getBoolState("dialogs.login.share gpu info", _shareGpuInfo); @@ -40,7 +39,7 @@ _LoginDialog::_LoginDialog( _password = settings.getStringState("dialogs.login.password", ""); if (!_userName.empty()) { LoginErrorCode errorCode; - if (!_networkController->login(errorCode, _userName, _password, getUserInfo())) { + if (!networkService.login(errorCode, _userName, _password, getUserInfo())) { if (errorCode != LoginErrorCode_UnconfirmedUser) { MessageDialog::getInstance().information("Error", "Login failed."); } @@ -131,8 +130,9 @@ void _LoginDialog::onLogin() LoginErrorCode errorCode; auto userInfo = getUserInfo(); + auto& networkService = NetworkService::getInstance(); - if (!_networkController->login(errorCode, _userName, _password, userInfo)) { + if (!networkService.login(errorCode, _userName, _password, userInfo)) { switch (errorCode) { case LoginErrorCode_UnconfirmedUser: { _activateUserDialog->open(_userName, _password, userInfo); diff --git a/source/Gui/LoginDialog.h b/source/Gui/LoginDialog.h index 3ffa6fa73..8b687e0b4 100644 --- a/source/Gui/LoginDialog.h +++ b/source/Gui/LoginDialog.h @@ -1,5 +1,7 @@ #pragma once +#include "Network/Definitions.h" + #include "AlienDialog.h" #include "Definitions.h" @@ -11,8 +13,7 @@ class _LoginDialog : public _AlienDialog BrowserWindow const& browserWindow, CreateUserDialog const& createUserDialog, ActivateUserDialog const& activateUserDialog, - ResetPasswordDialog const& resetPasswordDialog, - NetworkController const& networkController); + ResetPasswordDialog const& resetPasswordDialog); ~_LoginDialog(); bool isShareGpuInfo() const; @@ -29,7 +30,6 @@ class _LoginDialog : public _AlienDialog BrowserWindow _browserWindow; CreateUserDialog _createUserDialog; ActivateUserDialog _activateUserDialog; - NetworkController _networkController; ResetPasswordDialog _resetPasswordDialog; bool _shareGpuInfo = true; diff --git a/source/Gui/MainWindow.cpp b/source/Gui/MainWindow.cpp index bf152018f..378284e54 100644 --- a/source/Gui/MainWindow.cpp +++ b/source/Gui/MainWindow.cpp @@ -23,6 +23,7 @@ #include "EngineInterface/SerializerService.h" #include "EngineInterface/SimulationController.h" +#include "Network/NetworkService.h" #include "ModeController.h" #include "SimulationView.h" @@ -53,7 +54,6 @@ #include "PatternAnalysisDialog.h" #include "MessageDialog.h" #include "FpsController.h" -#include "NetworkController.h" #include "BrowserWindow.h" #include "LoginDialog.h" #include "UploadSimulationDialog.h" @@ -121,7 +121,6 @@ _MainWindow::_MainWindow(SimulationController const& simController, GuiLogger co _editorController = std::make_shared<_EditorController>(_simController, _viewport); _modeController = std::make_shared<_ModeController>(_editorController); - _networkController = std::make_shared<_NetworkController>(); _simulationView = std::make_shared<_SimulationView>(_simController, _modeController, _viewport, _editorController->getEditorModel()); simulationViewPtr = _simulationView.get(); _statisticsWindow = std::make_shared<_StatisticsWindow>(_simController); @@ -141,16 +140,16 @@ _MainWindow::_MainWindow(SimulationController const& simController, GuiLogger co _patternAnalysisDialog = std::make_shared<_PatternAnalysisDialog>(_simController); _fpsController = std::make_shared<_FpsController>(); _browserWindow = - std::make_shared<_BrowserWindow>(_simController, _networkController, _statisticsWindow, _viewport, _temporalControlWindow, _editorController); - _activateUserDialog = std::make_shared<_ActivateUserDialog>(_simController, _browserWindow, _networkController); - _createUserDialog = std::make_shared<_CreateUserDialog>(_activateUserDialog, _networkController); - _newPasswordDialog = std::make_shared<_NewPasswordDialog>(_simController, _browserWindow, _networkController); - _resetPasswordDialog = std::make_shared<_ResetPasswordDialog>(_newPasswordDialog, _networkController); - _loginDialog = std::make_shared<_LoginDialog>(_simController, _browserWindow, _createUserDialog, _activateUserDialog, _resetPasswordDialog, _networkController); + std::make_shared<_BrowserWindow>(_simController, _statisticsWindow, _viewport, _temporalControlWindow, _editorController); + _activateUserDialog = std::make_shared<_ActivateUserDialog>(_simController, _browserWindow); + _createUserDialog = std::make_shared<_CreateUserDialog>(_activateUserDialog); + _newPasswordDialog = std::make_shared<_NewPasswordDialog>(_simController, _browserWindow); + _resetPasswordDialog = std::make_shared<_ResetPasswordDialog>(_newPasswordDialog); + _loginDialog = std::make_shared<_LoginDialog>(_simController, _browserWindow, _createUserDialog, _activateUserDialog, _resetPasswordDialog); _uploadSimulationDialog = std::make_shared<_UploadSimulationDialog>( - _browserWindow, _loginDialog, _simController, _networkController, _viewport, _editorController->getGenomeEditorWindow()); - _deleteUserDialog = std::make_shared<_DeleteUserDialog>(_browserWindow, _networkController); - _networkSettingsDialog = std::make_shared<_NetworkSettingsDialog>(_browserWindow, _networkController); + _browserWindow, _loginDialog, _simController, _viewport, _editorController->getGenomeEditorWindow()); + _deleteUserDialog = std::make_shared<_DeleteUserDialog>(_browserWindow); + _networkSettingsDialog = std::make_shared<_NetworkSettingsDialog>(_browserWindow); _imageToPatternDialog = std::make_shared<_ImageToPatternDialog>(_viewport, _simController); _shaderWindow = std::make_shared<_ShaderWindow>(_simulationView); @@ -302,8 +301,7 @@ void _MainWindow::processLoadingSimulation() void _MainWindow::processLoadingControls() { - ImGui::PushStyleVar(ImGuiStyleVar_GrabMinSize, Const::SliderBarWidth); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 10); + pushGlobalStyle(); processMenubar(); processDialogs(); @@ -314,15 +312,14 @@ void _MainWindow::processLoadingControls() _simulationView->processControls(_renderSimulation); _startupController->process(); - ImGui::PopStyleVar(2); + popGlobalStyle(); renderSimulation(); } void _MainWindow::processReady() { - ImGui::PushStyleVar(ImGuiStyleVar_GrabMinSize, Const::SliderBarWidth); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 10); + pushGlobalStyle(); processMenubar(); processDialogs(); @@ -331,7 +328,7 @@ void _MainWindow::processReady() _uiController->process(); _simulationView->processControls(_renderSimulation); - ImGui::PopStyleVar(2); + popGlobalStyle(); renderSimulation(); } @@ -357,6 +354,7 @@ void _MainWindow::processMenubar() auto creatorWindow = _editorController->getCreatorWindow(); auto multiplierWindow = _editorController->getMultiplierWindow(); auto genomeEditorWindow = _editorController->getGenomeEditorWindow(); + auto& networkService = NetworkService::getInstance(); if (ImGui::BeginMainMenuBar()) { if (AlienImGui::ShutdownButton()) { @@ -395,30 +393,30 @@ void _MainWindow::processMenubar() _browserWindow->setOn(!_browserWindow->isOn()); } ImGui::Separator(); - ImGui::BeginDisabled((bool)_networkController->getLoggedInUserName()); + ImGui::BeginDisabled((bool)networkService.getLoggedInUserName()); if (ImGui::MenuItem("Login", "ALT+L")) { _loginDialog->open(); } ImGui::EndDisabled(); - ImGui::BeginDisabled(!_networkController->getLoggedInUserName()); + ImGui::BeginDisabled(!networkService.getLoggedInUserName()); if (ImGui::MenuItem("Logout", "ALT+T")) { - _networkController->logout(); + networkService.logout(); _browserWindow->onRefresh(); } ImGui::EndDisabled(); - ImGui::BeginDisabled(!_networkController->getLoggedInUserName()); + ImGui::BeginDisabled(!networkService.getLoggedInUserName()); if (ImGui::MenuItem("Upload simulation", "ALT+D")) { - _uploadSimulationDialog->open(DataType_Simulation); + _uploadSimulationDialog->open(NetworkResourceType_Simulation); } ImGui::EndDisabled(); - ImGui::BeginDisabled(!_networkController->getLoggedInUserName()); + ImGui::BeginDisabled(!networkService.getLoggedInUserName()); if (ImGui::MenuItem("Upload genome", "ALT+Q")) { - _uploadSimulationDialog->open(DataType_Genome); + _uploadSimulationDialog->open(NetworkResourceType_Genome); } ImGui::EndDisabled(); ImGui::Separator(); - ImGui::BeginDisabled(!_networkController->getLoggedInUserName()); + ImGui::BeginDisabled(!networkService.getLoggedInUserName()); if (ImGui::MenuItem("Delete user", "ALT+J")) { _deleteUserDialog->open(); } @@ -589,20 +587,20 @@ void _MainWindow::processMenubar() if (io.KeyAlt && ImGui::IsKeyPressed(GLFW_KEY_W)) { _browserWindow->setOn(!_browserWindow->isOn()); } - if (io.KeyAlt && ImGui::IsKeyPressed(GLFW_KEY_L) && !_networkController->getLoggedInUserName()) { + if (io.KeyAlt && ImGui::IsKeyPressed(GLFW_KEY_L) && !networkService.getLoggedInUserName()) { _loginDialog->open(); } if (io.KeyAlt && ImGui::IsKeyPressed(GLFW_KEY_T)) { - _networkController->logout(); + networkService.logout(); _browserWindow->onRefresh(); } - if (io.KeyAlt && ImGui::IsKeyPressed(GLFW_KEY_D) && _networkController->getLoggedInUserName()) { - _uploadSimulationDialog->open(DataType_Simulation); + if (io.KeyAlt && ImGui::IsKeyPressed(GLFW_KEY_D) && networkService.getLoggedInUserName()) { + _uploadSimulationDialog->open(NetworkResourceType_Simulation); } - if (io.KeyAlt && ImGui::IsKeyPressed(GLFW_KEY_Q) && _networkController->getLoggedInUserName()) { - _uploadSimulationDialog->open(DataType_Genome); + if (io.KeyAlt && ImGui::IsKeyPressed(GLFW_KEY_Q) && networkService.getLoggedInUserName()) { + _uploadSimulationDialog->open(NetworkResourceType_Genome); } - if (io.KeyAlt && ImGui::IsKeyPressed(GLFW_KEY_J) && _networkController->getLoggedInUserName()) { + if (io.KeyAlt && ImGui::IsKeyPressed(GLFW_KEY_J) && networkService.getLoggedInUserName()) { _deleteUserDialog->open(); } @@ -746,7 +744,6 @@ void _MainWindow::processControllers() { _autosaveController->process(); _editorController->process(); - _networkController->process(); OverlayMessageController::getInstance().process(); DelayedExecutionController::getInstance().process(); } @@ -833,3 +830,18 @@ void _MainWindow::onPauseSimulation() { _simController->pauseSimulation(); } + +void _MainWindow::pushGlobalStyle() +{ + ImGui::PushStyleVar(ImGuiStyleVar_GrabMinSize, Const::SliderBarWidth); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, Const::WindowsRounding); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, (ImVec4)Const::HeaderHoveredColor); + ImGui::PushStyleColor(ImGuiCol_HeaderActive, (ImVec4)Const::HeaderActiveColor); + ImGui::PushStyleColor(ImGuiCol_Header, (ImVec4)Const::HeaderColor); +} + +void _MainWindow::popGlobalStyle() +{ + ImGui::PopStyleColor(3); + ImGui::PopStyleVar(2); +} diff --git a/source/Gui/MainWindow.h b/source/Gui/MainWindow.h index a67ce56d1..c4bace529 100644 --- a/source/Gui/MainWindow.h +++ b/source/Gui/MainWindow.h @@ -1,6 +1,8 @@ #pragma once #include "EngineInterface/Definitions.h" +#include "Network/Definitions.h" + #include "Definitions.h" class _MainWindow @@ -31,6 +33,9 @@ class _MainWindow void onRunSimulation(); void onPauseSimulation(); + void pushGlobalStyle(); + void popGlobalStyle(); + GLFWwindow* _window; GuiLogger _logger; @@ -71,7 +76,6 @@ class _MainWindow UiController _uiController; EditorController _editorController; FpsController _fpsController; - NetworkController _networkController; bool _onExit = false; bool _simulationMenuToggled = false; diff --git a/source/Gui/NetworkDataParser.cpp b/source/Gui/NetworkDataParser.cpp deleted file mode 100644 index ae426976a..000000000 --- a/source/Gui/NetworkDataParser.cpp +++ /dev/null @@ -1,54 +0,0 @@ -#include "NetworkDataParser.h" - -std::vector NetworkDataParser::decodeRemoteSimulationData(boost::property_tree::ptree const& tree) -{ - std::vector result; - for (auto const& [key, subTree] : tree) { - RemoteSimulationData entry; - entry.id = subTree.get("id"); - entry.userName = subTree.get("userName"); - entry.simName = subTree.get("simulationName"); - entry.description= subTree.get("description"); - entry.width = subTree.get("width"); - entry.height = subTree.get("height"); - entry.particles = subTree.get("particles"); - entry.version = subTree.get("version"); - entry.timestamp = subTree.get("timestamp"); - entry.contentSize = std::stoll(subTree.get("contentSize")); - - bool isArray = false; - int counter = 0; - for (auto const& [likeTypeString, numLikesString] : subTree.get_child("likesByType")) { - auto likes = std::stoi(numLikesString.data()); - if (likeTypeString.empty()) { - isArray = true; - } - auto likeType = isArray ? counter : std::stoi(likeTypeString); - entry.numLikesByEmojiType[likeType] = likes; - ++counter; - } - entry.numDownloads = subTree.get("numDownloads"); - entry.fromRelease = subTree.get("fromRelease") == 1; - entry.type = subTree.get("type"); - result.emplace_back(entry); - } - return result; -} - -std::vector NetworkDataParser::decodeUserData(boost::property_tree::ptree const& tree) -{ - std::vector result; - for (auto const& [key, subTree] : tree) { - UserData entry; - entry.userName = subTree.get("userName"); - entry.starsReceived = subTree.get("starsReceived"); - entry.starsGiven = subTree.get("starsGiven"); - entry.timestamp = subTree.get("timestamp"); - entry.online = subTree.get("online"); - entry.lastDayOnline = subTree.get("lastDayOnline"); - entry.timeSpent = subTree.get("timeSpent"); - entry.gpu = subTree.get("gpu"); - result.emplace_back(entry); - } - return result; -} diff --git a/source/Gui/NetworkDataParser.h b/source/Gui/NetworkDataParser.h deleted file mode 100644 index 08b333b7e..000000000 --- a/source/Gui/NetworkDataParser.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include -#include - -#include "RemoteSimulationData.h" -#include "UserData.h" - -class NetworkDataParser -{ -public: - static std::vector decodeRemoteSimulationData(boost::property_tree::ptree const& tree); - static std::vector decodeUserData(boost::property_tree::ptree const& tree); -}; \ No newline at end of file diff --git a/source/Gui/NetworkSettingsDialog.cpp b/source/Gui/NetworkSettingsDialog.cpp index c3892cf3a..4be87dde7 100644 --- a/source/Gui/NetworkSettingsDialog.cpp +++ b/source/Gui/NetworkSettingsDialog.cpp @@ -2,8 +2,9 @@ #include +#include "Network/NetworkService.h" + #include "AlienImGui.h" -#include "NetworkController.h" #include "BrowserWindow.h" #include "StyleRepository.h" @@ -12,10 +13,9 @@ namespace auto const RightColumnWidth = 150.0f; } -_NetworkSettingsDialog::_NetworkSettingsDialog(BrowserWindow const& browserWindow, NetworkController const& networkController) +_NetworkSettingsDialog::_NetworkSettingsDialog(BrowserWindow const& browserWindow) : _AlienDialog("Network settings") , _browserWindow(browserWindow) - , _networkController(networkController) { } @@ -41,12 +41,14 @@ void _NetworkSettingsDialog::processIntern() void _NetworkSettingsDialog::openIntern() { - _origServerAddress = _networkController->getServerAddress(); + auto& networkService = NetworkService::getInstance(); + _origServerAddress = networkService.getServerAddress(); _serverAddress = _origServerAddress; } void _NetworkSettingsDialog::onChangeSettings() { - _networkController->setServerAddress(_serverAddress); + auto& networkService = NetworkService::getInstance(); + networkService.setServerAddress(_serverAddress); _browserWindow->onRefresh(); } diff --git a/source/Gui/NetworkSettingsDialog.h b/source/Gui/NetworkSettingsDialog.h index 48365553b..790fbe461 100644 --- a/source/Gui/NetworkSettingsDialog.h +++ b/source/Gui/NetworkSettingsDialog.h @@ -1,12 +1,14 @@ #pragma once +#include "Network/Definitions.h" + #include "AlienDialog.h" #include "Definitions.h" class _NetworkSettingsDialog : public _AlienDialog { public: - _NetworkSettingsDialog(BrowserWindow const& browserWindow, NetworkController const& networkController); + _NetworkSettingsDialog(BrowserWindow const& browserWindow); private: void processIntern(); @@ -15,7 +17,6 @@ class _NetworkSettingsDialog : public _AlienDialog void onChangeSettings(); BrowserWindow _browserWindow; - NetworkController _networkController; std::string _serverAddress; std::string _origServerAddress; diff --git a/source/Gui/NewPasswordDialog.cpp b/source/Gui/NewPasswordDialog.cpp index 5d0068827..a7015d5e5 100644 --- a/source/Gui/NewPasswordDialog.cpp +++ b/source/Gui/NewPasswordDialog.cpp @@ -3,20 +3,18 @@ #include #include "EngineInterface/SimulationController.h" +#include "Network/NetworkService.h" #include "AlienImGui.h" #include "BrowserWindow.h" #include "MessageDialog.h" -#include "NetworkController.h" _NewPasswordDialog::_NewPasswordDialog( SimulationController const& simController, - BrowserWindow const& browserWindow, - NetworkController const& networkController) + BrowserWindow const& browserWindow) : _AlienDialog("New password") , _simController(simController) , _browserWindow(browserWindow) - , _networkController(networkController) {} void _NewPasswordDialog::open(std::string const& userName, UserInfo const& userInfo) @@ -60,10 +58,11 @@ void _NewPasswordDialog::processIntern() void _NewPasswordDialog::onNewPassword() { - auto result = _networkController->setNewPassword(_userName, _newPassword, _confirmationCode); + auto& networkService = NetworkService::getInstance(); + auto result = networkService.setNewPassword(_userName, _newPassword, _confirmationCode); if (result) { LoginErrorCode errorCode; - result |= _networkController->login(errorCode, _userName, _newPassword, _userInfo); + result |= networkService.login(errorCode, _userName, _newPassword, _userInfo); } if (!result) { MessageDialog::getInstance().information("Error", "An error occurred on the server. Your entered code may be incorrect.\nPlease try to reset the password again."); diff --git a/source/Gui/NewPasswordDialog.h b/source/Gui/NewPasswordDialog.h index 786557aea..36ca4b5af 100644 --- a/source/Gui/NewPasswordDialog.h +++ b/source/Gui/NewPasswordDialog.h @@ -1,13 +1,14 @@ #pragma once +#include "Network/NetworkService.h" + #include "AlienDialog.h" #include "Definitions.h" -#include "NetworkController.h" class _NewPasswordDialog : public _AlienDialog { public: - _NewPasswordDialog(SimulationController const& simController, BrowserWindow const& browserWindow, NetworkController const& networkController); + _NewPasswordDialog(SimulationController const& simController, BrowserWindow const& browserWindow); void open(std::string const& userName, UserInfo const& userInfo); @@ -18,7 +19,6 @@ class _NewPasswordDialog : public _AlienDialog SimulationController _simController; BrowserWindow _browserWindow; - NetworkController _networkController; std::string _userName; std::string _newPassword; diff --git a/source/Gui/OverlayMessageController.cpp b/source/Gui/OverlayMessageController.cpp index 88a552f58..7c397979b 100644 --- a/source/Gui/OverlayMessageController.cpp +++ b/source/Gui/OverlayMessageController.cpp @@ -36,7 +36,7 @@ void OverlayMessageController::process() ImDrawList* drawList = ImGui::GetForegroundDrawList(); - auto styleRep = StyleRepository::getInstance(); + auto& styleRep = StyleRepository::getInstance(); auto center = ImGui::GetMainViewport()->Size; center.x /= 2; auto textColorFront = ImColor::HSV(0.5f, 0.0f, 1.0f, alpha); diff --git a/source/Gui/RemoteSimulationData.cpp b/source/Gui/RemoteSimulationData.cpp deleted file mode 100644 index 310b75f37..000000000 --- a/source/Gui/RemoteSimulationData.cpp +++ /dev/null @@ -1,102 +0,0 @@ -#include "RemoteSimulationData.h" - -#include -#include - -int RemoteSimulationData::compare(void const* left, void const* right, ImGuiTableSortSpecs const* specs) -{ - auto leftImpl = reinterpret_cast(left); - auto rightImpl = reinterpret_cast(right); - for (int n = 0; n < specs->SpecsCount; n++) { - const ImGuiTableColumnSortSpecs* sortSpec = &specs->Specs[n]; - int delta = 0; - switch (sortSpec->ColumnUserID) { - case RemoteSimulationDataColumnId_Timestamp: - delta = leftImpl->timestamp.compare(rightImpl->timestamp); - break; - case RemoteSimulationDataColumnId_UserName: - delta = leftImpl->userName.compare(rightImpl->userName); - break; - case RemoteSimulationDataColumnId_SimulationName: - delta = leftImpl->simName.compare(rightImpl->simName); - break; - case RemoteSimulationDataColumnId_Description: - delta = leftImpl->description.compare(rightImpl->description); - break; - case RemoteSimulationDataColumnId_Likes: - delta = leftImpl->getTotalLikes() - rightImpl->getTotalLikes(); - break; - case RemoteSimulationDataColumnId_NumDownloads: - delta = leftImpl->numDownloads - rightImpl->numDownloads; - break; - case RemoteSimulationDataColumnId_Width: - delta = leftImpl->width - rightImpl->width; - break; - case RemoteSimulationDataColumnId_Height: - delta = leftImpl->height - rightImpl->height; - break; - case RemoteSimulationDataColumnId_Particles: - delta = leftImpl->particles - rightImpl->particles; - break; - case RemoteSimulationDataColumnId_FileSize: - delta = static_cast(leftImpl->contentSize / 1024) - static_cast(rightImpl->contentSize / 1024); - break; - case RemoteSimulationDataColumnId_Version: - delta = leftImpl->version.compare(rightImpl->version); - break; - } - if (delta > 0) { - return (sortSpec->SortDirection == ImGuiSortDirection_Ascending) ? +1 : -1; - } - if (delta < 0) { - return (sortSpec->SortDirection == ImGuiSortDirection_Ascending) ? -1 : +1; - } - } - - return 0; -} - -bool RemoteSimulationData::matchWithFilter(std::string const& filter) const -{ - auto match = false; - if (timestamp.find(filter) != std::string::npos) { - match = true; - } - if (userName.find(filter) != std::string::npos) { - match = true; - } - if (simName.find(filter) != std::string::npos) { - match = true; - } - if (std::to_string(numDownloads).find(filter) != std::string::npos) { - match = true; - } - if (std::to_string(width).find(filter) != std::string::npos) { - match = true; - } - if (std::to_string(height).find(filter) != std::string::npos) { - match = true; - } - if (std::to_string(particles).find(filter) != std::string::npos) { - match = true; - } - if (std::to_string(contentSize).find(filter) != std::string::npos) { - match = true; - } - if (description.find(filter) != std::string::npos) { - match = true; - } - if (version.find(filter) != std::string::npos) { - match = true; - } - return match; -} - -int RemoteSimulationData::getTotalLikes() const -{ - int result = 0; - for (auto const& numReactions : numLikesByEmojiType | std::views::values) { - result += numReactions; - } - return result; -} diff --git a/source/Gui/RemoteSimulationData.h b/source/Gui/RemoteSimulationData.h deleted file mode 100644 index 1f8ddbda0..000000000 --- a/source/Gui/RemoteSimulationData.h +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once - -#include -#include - -class ImGuiTableSortSpecs; - -enum RemoteSimulationDataColumnId -{ - RemoteSimulationDataColumnId_Timestamp, - RemoteSimulationDataColumnId_UserName, - RemoteSimulationDataColumnId_SimulationName, - RemoteSimulationDataColumnId_Description, - RemoteSimulationDataColumnId_Likes, - RemoteSimulationDataColumnId_NumDownloads, - RemoteSimulationDataColumnId_Width, - RemoteSimulationDataColumnId_Height, - RemoteSimulationDataColumnId_Particles, - RemoteSimulationDataColumnId_FileSize, - RemoteSimulationDataColumnId_Version, - RemoteSimulationDataColumnId_Actions -}; - -using RemoteDataType = int; -enum RemoteDataType_ -{ - RemoteDataType_Simulation, - RemoteDataType_Genome -}; - -class RemoteSimulationData -{ -public: - std::string id; - std::string timestamp; - std::string userName; - std::string simName; - std::map numLikesByEmojiType; - int numDownloads; - int width; - int height; - int particles; - uint64_t contentSize; - std::string description; - std::string version; - bool fromRelease; - RemoteDataType type; - - static int compare(void const* left, void const* right, ImGuiTableSortSpecs const* specs); - bool matchWithFilter(std::string const& filter) const; - - int getTotalLikes() const; -}; diff --git a/source/Gui/ResetPasswordDialog.cpp b/source/Gui/ResetPasswordDialog.cpp index 7ee94c5f1..6c09d8c8b 100644 --- a/source/Gui/ResetPasswordDialog.cpp +++ b/source/Gui/ResetPasswordDialog.cpp @@ -6,9 +6,8 @@ #include "MessageDialog.h" #include "NewPasswordDialog.h" -_ResetPasswordDialog::_ResetPasswordDialog(NewPasswordDialog const& newPasswordDialog, NetworkController const& networkController) +_ResetPasswordDialog::_ResetPasswordDialog(NewPasswordDialog const& newPasswordDialog) : _AlienDialog("Reset password") - , _networkController(networkController) , _newPasswordDialog(newPasswordDialog) {} @@ -53,7 +52,9 @@ void _ResetPasswordDialog::processIntern() void _ResetPasswordDialog::onResetPassword() { - if (_networkController->resetPassword(_userName, _email)) { + auto& networkService = NetworkService::getInstance(); + + if (networkService.resetPassword(_userName, _email)) { _newPasswordDialog->open(_userName, _userInfo); } else { MessageDialog::getInstance().information( diff --git a/source/Gui/ResetPasswordDialog.h b/source/Gui/ResetPasswordDialog.h index 86c248889..f051ac3e4 100644 --- a/source/Gui/ResetPasswordDialog.h +++ b/source/Gui/ResetPasswordDialog.h @@ -1,13 +1,14 @@ #pragma once +#include "Network/NetworkService.h" + #include "AlienDialog.h" #include "Definitions.h" -#include "NetworkController.h" class _ResetPasswordDialog : public _AlienDialog { public: - _ResetPasswordDialog(NewPasswordDialog const& newPasswordDialog, NetworkController const& networkController); + _ResetPasswordDialog(NewPasswordDialog const& newPasswordDialog); void open(std::string const& userName, UserInfo const& userInfo); @@ -17,7 +18,6 @@ class _ResetPasswordDialog : public _AlienDialog void onResetPassword(); NewPasswordDialog _newPasswordDialog; - NetworkController _networkController; std::string _userName; std::string _email; diff --git a/source/Gui/SimulationView.cpp b/source/Gui/SimulationView.cpp index c4e7e4610..d9e25788e 100644 --- a/source/Gui/SimulationView.cpp +++ b/source/Gui/SimulationView.cpp @@ -331,7 +331,7 @@ void _SimulationView::draw(bool renderSimulation) auto textWidth = scale(300.0f); auto textHeight = scale(80.0f); ImDrawList* drawList = ImGui::GetBackgroundDrawList(); - auto styleRep = StyleRepository::getInstance(); + auto& styleRep = StyleRepository::getInstance(); auto right = ImGui::GetMainViewport()->Pos.x + ImGui::GetMainViewport()->Size.x; auto bottom = ImGui::GetMainViewport()->Pos.y + ImGui::GetMainViewport()->Size.y; auto maxLength = std::max(right, bottom); diff --git a/source/Gui/StartupController.cpp b/source/Gui/StartupController.cpp index 37289b28e..5c8621b66 100644 --- a/source/Gui/StartupController.cpp +++ b/source/Gui/StartupController.cpp @@ -125,7 +125,7 @@ void _StartupController::activate() void _StartupController::processLoadingScreen() { - auto styleRep = StyleRepository::getInstance(); + auto& styleRep = StyleRepository::getInstance(); auto center = ImGui::GetMainViewport()->GetCenter(); auto bottom = ImGui::GetMainViewport()->Pos.y + ImGui::GetMainViewport()->Size.y; ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); diff --git a/source/Gui/StyleRepository.h b/source/Gui/StyleRepository.h index fecf78496..733bed14f 100644 --- a/source/Gui/StyleRepository.h +++ b/source/Gui/StyleRepository.h @@ -17,6 +17,11 @@ namespace Const int64_t const TextDecentColor = 0xff909090; int64_t const TextInfoColor = 0xff308787; + ImColor const HeaderColor = ImColor::HSV(0.58f, 0.7f, 0.3f); + ImColor const HeaderActiveColor = ImColor::HSV(0.58f, 0.7f, 0.5f); + ImColor const HeaderHoveredColor = ImColor::HSV(0.58f, 0.7f, 0.4f); + + ImColor const MenuButtonColor = ImColor::HSV(0.6f, 0.6f, 0.5f); ImColor const MenuButtonHoveredColor = ImColor::HSV(0.6f, 1.0f, 1.0f); ImColor const MenuButtonActiveColor = ImColor::HSV(0.6f, 0.8f, 0.7f); @@ -55,18 +60,9 @@ namespace Const ImColor const InspectorLineColor = ImColor::HSV(0.54f, 0.0f, 1.0f, 1.0f); ImColor const InspectorRectColor = ImColor::HSV(0.54f, 0.0f, 0.5f, 1.0f); - ImColor const LikeButtonTextColor = ImColor::HSV(0.16f, 1.0f, 1.0f, 1.0f); - ImColor const NoLikeButtonTextColor = ImColor::HSV(0.16f, 0.5f, 0.5f, 1.0f); - ImColor const DownloadButtonTextColor = ImColor::HSV(0.55f, 0.6f, 1.0f, 1.0f); - ImColor const DeleteButtonTextColor = ImColor::HSV(0.0f, 0.6f, 0.8f, 1.0f); - ImColor const NavigationCursorColor = ImColor::HSV(0, 0.0f, 1.0f, 0.4f); ImColor const EditCursorColor = ImColor::HSV(0.6, 0.6f, 1.0f, 0.7f); - ImColor const VersionOkColor = ImColor::HSV(0.58f, 0.0f, 1.0f); - ImColor const VersionOutdatedColor = ImColor::HSV(0.0f, 0.0f, 0.6f); - ImColor const VersionNewerColor = ImColor::HSV(0.0f, 0.2f, 1.0f); - ImColor const GenomePreviewConnectionColor = ImColor::HSV(0, 0, 0.5f); ImColor const GenomePreviewDotSymbolColor = ImColor::HSV(0, 0, 0.7f); ImColor const GenomePreviewInfinitySymbolColor = ImColor::HSV(0, 0, 0.7f); @@ -80,14 +76,28 @@ namespace Const ImColor const NeuronEditorZeroLinePlotColor = ImColor::HSV(0.6f, 1.0f, 0.7f); ImColor const NeuronEditorPlotColor = ImColor::HSV(0.0f, 0.0f, 1.0f); + ImColor const BrowserAddReactionButtonTextColor = ImColor::HSV(0.375f, 0.6f, 0.7f, 1.0f); + ImColor const BrowserDownloadButtonTextColor = ImColor::HSV(0.55f, 0.6f, 1.0f, 1.0f); + ImColor const BrowserDeleteButtonTextColor = ImColor::HSV(0.0f, 0.6f, 0.8f, 1.0f); + ImColor const BrowserFolderTextColor = ImColor::HSV(0.0f, 0.0f, 1.0f); + ImColor const BrowserFolderLineColor = ImColor::HSV(0.0f, 0.0f, 0.5f); + ImColor const BrowserFolderPropertiesTextColor = ImColor::HSV(0.0f, 0.0f, 0.5f, 1.0f); + ImColor const BrowserFolderSymbolColor = ImColor::HSV(0.0f, 0.0f, 1.0f, 1.0f); + + ImColor const BrowserVersionOkTextColor = ImColor::HSV(0.58f, 0.2f, 1.0f); + ImColor const BrowserVersionOutdatedTextColor = ImColor::HSV(0.0f, 0.0f, 0.6f); + ImColor const BrowserVersionNewerTextColor = ImColor::HSV(0.0f, 0.2f, 1.0f); + float const WindowAlpha = 0.9f; float const SliderBarWidth = 30.0f; + float const WindowsRounding = 10.0f; } class StyleRepository { public: static StyleRepository& getInstance(); + StyleRepository(StyleRepository const&) = delete; void init(); diff --git a/source/Gui/UploadSimulationDialog.cpp b/source/Gui/UploadSimulationDialog.cpp index a169a0d4f..ea9bc19ba 100644 --- a/source/Gui/UploadSimulationDialog.cpp +++ b/source/Gui/UploadSimulationDialog.cpp @@ -6,10 +6,10 @@ #include "EngineInterface/SerializerService.h" #include "EngineInterface/SimulationController.h" #include "EngineInterface/GenomeDescriptionService.h" +#include "Network/NetworkService.h" #include "AlienImGui.h" #include "MessageDialog.h" -#include "NetworkController.h" #include "StyleRepository.h" #include "BrowserWindow.h" #include "DelayedExecutionController.h" @@ -20,46 +20,48 @@ namespace { - std::map const BrowserDataTypeToLowerString = { - {DataType_Simulation, "simulation"}, - {DataType_Genome, "genome"}}; - std::map const BrowserDataTypeToUpperString = { - {DataType_Simulation, "Simulation"}, - {DataType_Genome, "Genome"}}; + auto constexpr FolderWidgetHeight = 50.0f; + + std::map const BrowserDataTypeToLowerString = { + {NetworkResourceType_Simulation, "simulation"}, + {NetworkResourceType_Genome, "genome"}}; + std::map const BrowserDataTypeToUpperString = { + {NetworkResourceType_Simulation, "Simulation"}, + {NetworkResourceType_Genome, "Genome"}}; } _UploadSimulationDialog::_UploadSimulationDialog( BrowserWindow const& browserWindow, LoginDialog const& loginDialog, SimulationController const& simController, - NetworkController const& networkController, Viewport const& viewport, GenomeEditorWindow const& genomeEditorWindow) : _AlienDialog("") , _simController(simController) - , _networkController(networkController) , _browserWindow(browserWindow) , _loginDialog(loginDialog) , _viewport(viewport) , _genomeEditorWindow(genomeEditorWindow) { auto& settings = GlobalSettings::getInstance(); - _simName = settings.getStringState("dialogs.upload.simulation name", ""); - _simDescription = settings.getStringState("dialogs.upload.simulation description", ""); + _resourceName = settings.getStringState("dialogs.upload.simulation name", ""); + _resourceDescription = settings.getStringState("dialogs.upload.simulation description", ""); } _UploadSimulationDialog::~_UploadSimulationDialog() { auto& settings = GlobalSettings::getInstance(); - settings.setStringState("dialogs.upload.simulation name", _simName); - settings.setStringState("dialogs.upload.simulation description", _simDescription); + settings.setStringState("dialogs.upload.simulation name", _resourceName); + settings.setStringState("dialogs.upload.simulation description", _resourceDescription); } -void _UploadSimulationDialog::open(DataType dataType) +void _UploadSimulationDialog::open(NetworkResourceType dataType, std::string const& folder) { - if (_networkController->getLoggedInUserName()) { + auto& networkService = NetworkService::getInstance(); + if (networkService.getLoggedInUserName()) { changeTitle("Upload " + BrowserDataTypeToLowerString.at(dataType)); _dataType = dataType; + _folder = folder; _AlienDialog::open(); } else { _loginDialog->open(); @@ -68,24 +70,43 @@ void _UploadSimulationDialog::open(DataType dataType) void _UploadSimulationDialog::processIntern() { + auto resourceTypeString = BrowserDataTypeToLowerString.at(_dataType); AlienImGui::Text("Data privacy policy"); AlienImGui::HelpMarker( - "The " + BrowserDataTypeToLowerString.at(_dataType) - + " file, name and description are stored on the server. It cannot be guaranteed that the data will not be deleted."); + "The " + resourceTypeString + " file, name and description are stored on the server. It cannot be guaranteed that the data will not be deleted."); + + AlienImGui::Text("How to use or create folders?"); + AlienImGui::HelpMarker("If you want to upload the " + resourceTypeString + + " to a folder, you can use the `/`-notation. The folder will be created automatically if it does not exist.\nFor instance, naming a simulation as `Biome/Water " + "world/Initial/Variant 1` will create the nested folders `Biome`, `Water world` and `Initial`."); + AlienImGui::Separator(); - AlienImGui::InputText(AlienImGui::InputTextParameters().hint(BrowserDataTypeToUpperString.at(_dataType) + " name").textWidth(0), _simName); + if (!_folder.empty()) { + std::string text = "The following folder has been selected and will used for the upload:\n" + _folder; + ImGui::PushID("folder info"); + ImGui::BeginDisabled(); + AlienImGui::InputTextMultiline(AlienImGui::InputTextMultilineParameters().hint(_folder).textWidth(0).height(FolderWidgetHeight), text); + ImGui::EndDisabled(); + ImGui::PopID(); + } + + AlienImGui::InputText(AlienImGui::InputTextParameters().hint(BrowserDataTypeToUpperString.at(_dataType) + " name").textWidth(0), _resourceName); + AlienImGui::Separator(); + + ImGui::PushID("description"); AlienImGui::InputTextMultiline( AlienImGui::InputTextMultilineParameters() .hint("Description (optional)") .textWidth(0) .height(ImGui::GetContentRegionAvail().y - StyleRepository::getInstance().scale(50.0f)), - _simDescription); + _resourceDescription); + ImGui::PopID(); AlienImGui::Separator(); - ImGui::BeginDisabled(_simName.empty()); + ImGui::BeginDisabled(_resourceName.empty()); if (AlienImGui::Button("OK")) { close(); onUpload(); @@ -96,15 +117,15 @@ void _UploadSimulationDialog::processIntern() ImGui::SameLine(); if (AlienImGui::Button("Cancel")) { close(); - _simName = _origSimName; - _simDescription = _origSimDescription; + _resourceName = _origResourceName; + _resourceDescription = _origResourceDescription; } } void _UploadSimulationDialog::openIntern() { - _origSimName = _simName; - _origSimDescription = _simDescription; + _origResourceName = _resourceName; + _origResourceDescription = _resourceDescription; } void _UploadSimulationDialog::onUpload() @@ -118,7 +139,7 @@ void _UploadSimulationDialog::onUpload() IntVector2D size; int numObjects = 0; - if (_dataType == DataType_Simulation) { + if (_dataType == NetworkResourceType_Simulation) { DeserializedSimulation deserializedSim; deserializedSim.auxiliaryData.timestep = static_cast(_simController->getCurrentTimestep()); deserializedSim.auxiliaryData.zoom = _viewport->getZoomFactor(); @@ -154,7 +175,8 @@ void _UploadSimulationDialog::onUpload() } } - if (!_networkController->uploadSimulation(_simName, _simDescription, size, numObjects, mainData, settings, statistics, _dataType)) { + auto& networkService = NetworkService::getInstance(); + if (!networkService.uploadSimulation(_folder + _resourceName, _resourceDescription, size, numObjects, mainData, settings, statistics, _dataType)) { showMessage("Error", "Failed to upload " + BrowserDataTypeToLowerString.at(_dataType) + "."); return; } diff --git a/source/Gui/UploadSimulationDialog.h b/source/Gui/UploadSimulationDialog.h index 4f18b98a2..c5f091fc5 100644 --- a/source/Gui/UploadSimulationDialog.h +++ b/source/Gui/UploadSimulationDialog.h @@ -1,7 +1,9 @@ #pragma once -#include "AlienDialog.h" #include "EngineInterface/Definitions.h" +#include "Network/Definitions.h" + +#include "AlienDialog.h" #include "Definitions.h" class _UploadSimulationDialog : public _AlienDialog @@ -11,12 +13,11 @@ class _UploadSimulationDialog : public _AlienDialog BrowserWindow const& browserWindow, LoginDialog const& loginDialog, SimulationController const& simController, - NetworkController const& networkController, Viewport const& viewport, GenomeEditorWindow const& genomeEditorWindow); ~_UploadSimulationDialog(); - void open(DataType dataType); + void open(NetworkResourceType dataType, std::string const& folder = ""); private: void processIntern(); @@ -24,18 +25,18 @@ class _UploadSimulationDialog : public _AlienDialog void onUpload(); - std::string _simName; - std::string _simDescription; + std::string _folder; + std::string _resourceName; + std::string _resourceDescription; - std::string _origSimName; - std::string _origSimDescription; + std::string _origResourceName; + std::string _origResourceDescription; - DataType _dataType = DataType_Simulation; + NetworkResourceType _dataType = NetworkResourceType_Simulation; BrowserWindow _browserWindow; LoginDialog _loginDialog; SimulationController _simController; Viewport _viewport; - NetworkController _networkController; GenomeEditorWindow _genomeEditorWindow; }; \ No newline at end of file diff --git a/source/Network/CMakeLists.txt b/source/Network/CMakeLists.txt new file mode 100644 index 000000000..d2fb0bce3 --- /dev/null +++ b/source/Network/CMakeLists.txt @@ -0,0 +1,21 @@ + +add_library(Network + Definitions.h + NetworkService.cpp + NetworkService.h + NetworkResourceParserService.cpp + NetworkResourceParserService.h + NetworkResourceRawTO.cpp + NetworkResourceRawTO.h + NetworkResourceService.cpp + NetworkResourceService.h + NetworkResourceTreeTO.cpp + NetworkResourceTreeTO.h + UserTO.h) + +target_link_libraries(Network Base) +target_link_libraries(Network Boost::boost) + +if (MSVC) + target_compile_options(Network PRIVATE "/MP") +endif() diff --git a/source/Network/Definitions.h b/source/Network/Definitions.h new file mode 100644 index 000000000..17ff974c3 --- /dev/null +++ b/source/Network/Definitions.h @@ -0,0 +1,19 @@ +#pragma once + +#include "Base/Definitions.h" + +class NetworkService; + +using NetworkResourceType = int; +enum NetworkResourceType_ +{ + NetworkResourceType_Simulation, + NetworkResourceType_Genome +}; + + +struct _NetworkResourceRawTO; +using NetworkResourceRawTO = std::shared_ptr<_NetworkResourceRawTO>; + +struct _NetworkResourceTreeTO; +using NetworkResourceTreeTO = std::shared_ptr<_NetworkResourceTreeTO>; diff --git a/source/Network/NetworkResourceParserService.cpp b/source/Network/NetworkResourceParserService.cpp new file mode 100644 index 000000000..f5b6e0261 --- /dev/null +++ b/source/Network/NetworkResourceParserService.cpp @@ -0,0 +1,56 @@ +#include "NetworkResourceParserService.h" + +#include "NetworkResourceRawTO.h" + +std::vector NetworkResourceParserService::decodeRemoteSimulationData(boost::property_tree::ptree const& tree) +{ + std::vector result; + for (auto const& [key, subTree] : tree) { + auto entry = std::make_shared<_NetworkResourceRawTO>(); + entry->id = subTree.get("id"); + entry->userName = subTree.get("userName"); + entry->resourceName = subTree.get("simulationName"); + entry->description= subTree.get("description"); + entry->width = subTree.get("width"); + entry->height = subTree.get("height"); + entry->particles = subTree.get("particles"); + entry->version = subTree.get("version"); + entry->timestamp = subTree.get("timestamp"); + entry->contentSize = std::stoll(subTree.get("contentSize")); + + bool isArray = false; + int counter = 0; + for (auto const& [likeTypeString, numLikesString] : subTree.get_child("likesByType")) { + auto likes = std::stoi(numLikesString.data()); + if (likeTypeString.empty()) { + isArray = true; + } + auto likeType = isArray ? counter : std::stoi(likeTypeString); + entry->numLikesByEmojiType[likeType] = likes; + ++counter; + } + entry->numDownloads = subTree.get("numDownloads"); + entry->fromRelease = subTree.get("fromRelease") == 1; + entry->type = subTree.get("type"); + result.emplace_back(entry); + } + return result; +} + +std::vector NetworkResourceParserService::decodeUserData(boost::property_tree::ptree const& tree) +{ + std::vector result; + for (auto const& [key, subTree] : tree) { + UserTO entry; + entry.userName = subTree.get("userName"); + entry.starsReceived = subTree.get("starsReceived"); + entry.starsGiven = subTree.get("starsGiven"); + entry.timestamp = subTree.get("timestamp"); + entry.online = subTree.get("online"); + entry.lastDayOnline = subTree.get("lastDayOnline"); + entry.timeSpent = subTree.get("timeSpent"); + entry.gpu = subTree.get("gpu"); + result.emplace_back(entry); + } + return result; +} diff --git a/source/Network/NetworkResourceParserService.h b/source/Network/NetworkResourceParserService.h new file mode 100644 index 000000000..5029e6f0c --- /dev/null +++ b/source/Network/NetworkResourceParserService.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +#include "UserTO.h" +#include "Definitions.h" + +class NetworkResourceParserService +{ +public: + static std::vector decodeRemoteSimulationData(boost::property_tree::ptree const& tree); + static std::vector decodeUserData(boost::property_tree::ptree const& tree); +}; \ No newline at end of file diff --git a/source/Network/NetworkResourceRawTO.cpp b/source/Network/NetworkResourceRawTO.cpp new file mode 100644 index 000000000..8fa36b148 --- /dev/null +++ b/source/Network/NetworkResourceRawTO.cpp @@ -0,0 +1,100 @@ +#include "NetworkResourceRawTO.h" + +#include +#include + +int _NetworkResourceRawTO::compare(NetworkResourceRawTO const& left, NetworkResourceRawTO const& right, ImGuiTableSortSpecs const* specs) +{ + for (int n = 0; n < specs->SpecsCount; n++) { + const ImGuiTableColumnSortSpecs* sortSpec = &specs->Specs[n]; + int delta = 0; + switch (sortSpec->ColumnUserID) { + case NetworkResourceColumnId_Timestamp: + delta = left->timestamp.compare(right->timestamp); + break; + case NetworkResourceColumnId_UserName: + delta = left->userName.compare(right->userName); + break; + case NetworkResourceColumnId_SimulationName: + delta = left->resourceName.compare(right->resourceName); + break; + case NetworkResourceColumnId_Description: + delta = left->description.compare(right->description); + break; + case NetworkResourceColumnId_Likes: + delta = left->getTotalLikes() - right->getTotalLikes(); + break; + case NetworkResourceColumnId_NumDownloads: + delta = left->numDownloads - right->numDownloads; + break; + case NetworkResourceColumnId_Width: + delta = left->width - right->width; + break; + case NetworkResourceColumnId_Height: + delta = left->height - right->height; + break; + case NetworkResourceColumnId_Particles: + delta = left->particles - right->particles; + break; + case NetworkResourceColumnId_FileSize: + delta = static_cast(left->contentSize / 1024) - static_cast(right->contentSize / 1024); + break; + case NetworkResourceColumnId_Version: + delta = left->version.compare(right->version); + break; + } + if (delta > 0) { + return (sortSpec->SortDirection == ImGuiSortDirection_Ascending) ? +1 : -1; + } + if (delta < 0) { + return (sortSpec->SortDirection == ImGuiSortDirection_Ascending) ? -1 : +1; + } + } + + return 0; +} + +bool _NetworkResourceRawTO::matchWithFilter(std::string const& filter) const +{ + auto match = false; + if (timestamp.find(filter) != std::string::npos) { + match = true; + } + if (userName.find(filter) != std::string::npos) { + match = true; + } + if (resourceName.find(filter) != std::string::npos) { + match = true; + } + if (std::to_string(numDownloads).find(filter) != std::string::npos) { + match = true; + } + if (std::to_string(width).find(filter) != std::string::npos) { + match = true; + } + if (std::to_string(height).find(filter) != std::string::npos) { + match = true; + } + if (std::to_string(particles).find(filter) != std::string::npos) { + match = true; + } + if (std::to_string(contentSize).find(filter) != std::string::npos) { + match = true; + } + if (description.find(filter) != std::string::npos) { + match = true; + } + if (version.find(filter) != std::string::npos) { + match = true; + } + return match; +} + +int _NetworkResourceRawTO::getTotalLikes() const +{ + int result = 0; + for (auto const& numReactions : numLikesByEmojiType | std::views::values) { + result += numReactions; + } + return result; +} diff --git a/source/Network/NetworkResourceRawTO.h b/source/Network/NetworkResourceRawTO.h new file mode 100644 index 000000000..0067791d2 --- /dev/null +++ b/source/Network/NetworkResourceRawTO.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include + +#include "Definitions.h" + +class ImGuiTableSortSpecs; + +enum NetworkResourceColumnId +{ + NetworkResourceColumnId_Timestamp, + NetworkResourceColumnId_UserName, + NetworkResourceColumnId_SimulationName, + NetworkResourceColumnId_Description, + NetworkResourceColumnId_Likes, + NetworkResourceColumnId_NumDownloads, + NetworkResourceColumnId_Width, + NetworkResourceColumnId_Height, + NetworkResourceColumnId_Particles, + NetworkResourceColumnId_FileSize, + NetworkResourceColumnId_Version, + NetworkResourceColumnId_Actions +}; + +struct _NetworkResourceRawTO +{ + std::string id; + std::string timestamp; + std::string userName; + std::string resourceName; + std::map numLikesByEmojiType; + int numDownloads; + int width; + int height; + int particles; + uint64_t contentSize; + std::string description; + std::string version; + bool fromRelease; + NetworkResourceType type; + + static int compare(NetworkResourceRawTO const& left, NetworkResourceRawTO const& right, ImGuiTableSortSpecs const* specs); + bool matchWithFilter(std::string const& filter) const; + + int getTotalLikes() const; +}; diff --git a/source/Network/NetworkResourceService.cpp b/source/Network/NetworkResourceService.cpp new file mode 100644 index 000000000..6cc9ee7de --- /dev/null +++ b/source/Network/NetworkResourceService.cpp @@ -0,0 +1,264 @@ +#include "NetworkResourceService.h" + +#include + +#include +#include + +#include "NetworkResourceTreeTO.h" + +namespace +{ + int getNumEqualFolders(std::vector const& folderNames, std::vector const& otherFolderNames) + { + auto equalFolders = 0; + auto numFolders = std::min(folderNames.size(), otherFolderNames.size()); + for (int i = 0; i < numFolders; ++i) { + if (folderNames[i] == otherFolderNames[i]) { + ++equalFolders; + } else { + return equalFolders; + } + } + return equalFolders; + } +} + +std::vector NetworkResourceService::createTreeTOs( + std::vector const& rawTOs, + std::set> const& collapsedFolderNames) +{ + std::list treeToList; + for (auto const& rawTO : rawTOs) { + + //parse folder names + std::vector folderNames; + std::string nameWithoutFolders; + boost::split(folderNames, rawTO->resourceName, boost::is_any_of("/")); + if (!folderNames.empty()) { + nameWithoutFolders = folderNames.back(); + folderNames.pop_back(); + } + + std::list::iterator bestMatchIter; + int bestMatchEqualFolders; + if (!treeToList.empty()) { + + //find matching node + auto searchIter = treeToList.end(); + bestMatchIter = searchIter; + bestMatchEqualFolders = -1; + for (int i = 0; i < treeToList.size(); ++i) { + --searchIter; + auto otherRawTO = *searchIter; + auto equalFolders = getNumEqualFolders(folderNames, otherRawTO->folderNames); + if (equalFolders < bestMatchEqualFolders) { + break; + } + if (equalFolders > bestMatchEqualFolders) { + bestMatchIter = searchIter; + bestMatchEqualFolders = equalFolders; + } + } + ++bestMatchIter; + } else { + bestMatchIter = treeToList.begin(); + bestMatchEqualFolders = 0; + } + + //insert folders + for (int i = bestMatchEqualFolders; i < folderNames.size(); ++i) { + auto treeTO = std::make_shared<_NetworkResourceTreeTO>(); + treeTO->folderNames = std::vector(folderNames.begin(), folderNames.begin() + i + 1); + treeTO->type = rawTO->type; + treeTO->node = BrowserFolder(); + bestMatchIter = treeToList.insert(bestMatchIter, treeTO); + ++bestMatchIter; + } + + //insert leaf + auto treeTO = std::make_shared<_NetworkResourceTreeTO>(); + BrowserLeaf leaf{.leafName = nameWithoutFolders, .rawTO = rawTO}; + treeTO->type = rawTO->type; + treeTO->folderNames = folderNames; + treeTO->node = leaf; + treeToList.insert(bestMatchIter, treeTO); + } + + //calc folder lines + std::vector treeTOs(treeToList.begin(), treeToList.end()); + for (int i = 0; i < treeTOs.size(); ++i) { + auto& treeTO = treeTOs.at(i); + + if (i == 0) { + if (!treeTO->isLeaf()) { + treeTO->treeSymbols.emplace_back(FolderTreeSymbols::Expanded); + } + } else { + auto const& prevTreeTO = treeTOs.at(i - 1); + auto numEqualFolders = getNumEqualFolders(treeTO->folderNames, prevTreeTO->folderNames); + + treeTO->treeSymbols.resize(treeTO->folderNames.size(), FolderTreeSymbols::None); + + //calc symbols at position numEqualFolders - 1 + if (numEqualFolders > 0) { + int f = numEqualFolders - 1; + if (prevTreeTO->treeSymbols.at(f) == FolderTreeSymbols::Expanded) { + treeTO->treeSymbols.at(f) = FolderTreeSymbols::End; + } else if (prevTreeTO->treeSymbols.at(f) == FolderTreeSymbols::End) { + prevTreeTO->treeSymbols.at(f) = FolderTreeSymbols::Branch; + treeTO->treeSymbols.at(f) = FolderTreeSymbols::End; + } else if (prevTreeTO->treeSymbols.at(f) == FolderTreeSymbols::Branch) { + treeTO->treeSymbols.at(f) = FolderTreeSymbols::End; + } else if (prevTreeTO->treeSymbols.at(f) == FolderTreeSymbols::None) { + for (int j = i - 1; j >= 0; --j) { + auto& otherTreeTO = treeTOs.at(j); + if (otherTreeTO->treeSymbols.at(f) == FolderTreeSymbols::None) { + otherTreeTO->treeSymbols.at(f) = FolderTreeSymbols::Continue; + } else if (otherTreeTO->treeSymbols.at(f) == FolderTreeSymbols::End) { + otherTreeTO->treeSymbols.at(f) = FolderTreeSymbols::Branch; + } else { + break; + } + } + + } + + } + + //calc symbols before position numEqualFolders - 1 + for (int f = 0; f < numEqualFolders - 1; ++f) { + if (prevTreeTO->treeSymbols.at(f) == FolderTreeSymbols::Branch) { + treeTO->treeSymbols.at(f) = FolderTreeSymbols::None; + } + } + + if (numEqualFolders < treeTO->folderNames.size()) { + CHECK(numEqualFolders + 1 == treeTO->folderNames.size()); + treeTO->treeSymbols.back() = FolderTreeSymbols::Expanded; + + if (numEqualFolders > 0 && numEqualFolders < prevTreeTO->folderNames.size()) { + treeTO->treeSymbols.at(numEqualFolders - 1) = FolderTreeSymbols::End; + bool noneFound = false; + for (int j = i - 1; j >= 0; --j) { + auto& otherTreeTO = treeTOs.at(j); + if (otherTreeTO->treeSymbols.at(numEqualFolders - 1) == FolderTreeSymbols::None) { + otherTreeTO->treeSymbols.at(numEqualFolders - 1) = FolderTreeSymbols::Continue; + noneFound = true; + } else if (noneFound && otherTreeTO->treeSymbols.at(numEqualFolders - 1) == FolderTreeSymbols::End) { + otherTreeTO->treeSymbols.at(numEqualFolders - 1) = FolderTreeSymbols::Branch; + } else { + break; + } + } + } + } + if (numEqualFolders > 0 && numEqualFolders < prevTreeTO->folderNames.size() && numEqualFolders == treeTO->folderNames.size()) { + treeTO->treeSymbols.back() = FolderTreeSymbols::End; + } + } + } + + //calc numLeafs and numReactions for folders + for (int i = toInt(treeTOs.size()) - 1; i > 0; --i) { + auto& treeTO = treeTOs.at(i); + if (treeTO->isLeaf()) { + int numReactions = 0; + for (auto const& count : treeTO->getLeaf().rawTO->numLikesByEmojiType | std::views::values) { + numReactions += count; + } + for (int j = i - 1; j >= 0; --j) { + auto& otherTreeTO = treeTOs.at(j); + auto numEqualFolders = getNumEqualFolders(treeTO->folderNames, otherTreeTO->folderNames); + if (numEqualFolders == 0) { + break; + } + if (numEqualFolders == otherTreeTO->folderNames.size() && !otherTreeTO->isLeaf()) { + auto& folder = otherTreeTO->getFolder(); + ++folder.numLeafs; + folder.numReactions += numReactions; + } + } + } + } + + //collapse items + std::unordered_set collapsedFolderStrings; + for(auto const& folderNames : collapsedFolderNames) { + collapsedFolderStrings.insert(boost::join(folderNames, "/")); + } + + std::vector result; + result.reserve(treeTOs.size()); + for (auto const& treeTO : treeTOs) { + auto isVisible = true; + + std::string folderString; + auto numSolderToCheck = treeTO->isLeaf() ? treeTO->folderNames.size() : treeTO->folderNames.size() - 1; + for (size_t i = 0; i < numSolderToCheck; ++i) { + folderString.append(treeTO->folderNames.at(i)); + if (collapsedFolderStrings.contains(folderString)) { + isVisible = false; + } + if (i < numSolderToCheck) { + folderString.append("/"); + } + } + + if (!treeTO->isLeaf()) { + auto folderString = boost::join(treeTO->folderNames, "/"); + if (collapsedFolderStrings.contains(folderString)) { + treeTO->treeSymbols.back() = FolderTreeSymbols::Collapsed; + } + } + if (isVisible) { + result.emplace_back(treeTO); + } + } + return result; +} + +std::set> NetworkResourceService::calcInitialCollapsedFolderNames(std::vector const& rawTOs) +{ + std::set> result; + for (auto const& rawTO : rawTOs) { + std::vector folderNames; + boost::split(folderNames, rawTO->resourceName, boost::is_any_of("/")); + for (int i = 0; i < toInt(folderNames.size()) - 2; ++i) { + result.insert(std::vector(folderNames.begin(), folderNames.begin() + 2 + i)); + } + } + return result; +} + +std::string NetworkResourceService::concatenateFolderNames(std::vector const& folderNames, bool withSlash) +{ + auto result = boost::join(folderNames, "/"); + if (withSlash) { + result.append("/"); + } + return result; +} + +std::string NetworkResourceService::convertFolderNamesToSettings(std::set> const& data) +{ + std::vector parts; + for (auto const& folderNames : data) { + parts.emplace_back(concatenateFolderNames(folderNames, false)); + } + return boost::join(parts, "\\"); +} + +std::set> NetworkResourceService::convertSettingsToFolderNames(std::string const& data) +{ + std::vector parts; + boost::split(parts, data, boost::is_any_of("\\")); + + std::set> result; + for (auto const& part : parts) { + std::vector splittedParts; + boost::split(splittedParts, part, boost::is_any_of("/")); + result.insert(splittedParts); + } + return result; +} diff --git a/source/Network/NetworkResourceService.h b/source/Network/NetworkResourceService.h new file mode 100644 index 000000000..d0d97da79 --- /dev/null +++ b/source/Network/NetworkResourceService.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +#include "Definitions.h" +#include "NetworkResourceRawTO.h" + +class NetworkResourceService +{ +public: + static std::vector createTreeTOs( + std::vector const& rawTOs, + std::set> const& collapsedFolderNames); + + static std::set> calcInitialCollapsedFolderNames(std::vector const& browserData); + + static std::string concatenateFolderNames(std::vector const& folderNames, bool withSlash); + static std::string convertFolderNamesToSettings(std::set> const& data); + static std::set> convertSettingsToFolderNames(std::string const& data); +}; diff --git a/source/Network/NetworkResourceTreeTO.cpp b/source/Network/NetworkResourceTreeTO.cpp new file mode 100644 index 000000000..aaeeed274 --- /dev/null +++ b/source/Network/NetworkResourceTreeTO.cpp @@ -0,0 +1,16 @@ +#include "NetworkResourceTreeTO.h" + +bool _NetworkResourceTreeTO::isLeaf() +{ + return std::holds_alternative(node); +} + +BrowserLeaf& _NetworkResourceTreeTO::getLeaf() +{ + return std::get(node); +} + +BrowserFolder& _NetworkResourceTreeTO::getFolder() +{ + return std::get(node); +} diff --git a/source/Network/NetworkResourceTreeTO.h b/source/Network/NetworkResourceTreeTO.h new file mode 100644 index 000000000..2a415b196 --- /dev/null +++ b/source/Network/NetworkResourceTreeTO.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include + +#include "Definitions.h" +#include "NetworkResourceRawTO.h" + +struct BrowserFolder +{ + int numLeafs; + int numReactions; +}; + +struct BrowserLeaf +{ + std::string leafName; + NetworkResourceRawTO rawTO; +}; + +enum class FolderTreeSymbols +{ + Collapsed, + Expanded, + Continue, + Branch, + End, + None +}; + +struct _NetworkResourceTreeTO +{ + NetworkResourceType type; + std::vector folderNames; + std::vector treeSymbols; + std::variant node; + + bool isLeaf(); + BrowserLeaf& getLeaf(); + BrowserFolder& getFolder(); +}; diff --git a/source/Gui/NetworkController.cpp b/source/Network/NetworkService.cpp similarity index 84% rename from source/Gui/NetworkController.cpp rename to source/Network/NetworkService.cpp index 0262606bb..70acb2fd4 100644 --- a/source/Gui/NetworkController.cpp +++ b/source/Network/NetworkService.cpp @@ -1,4 +1,4 @@ -#include "NetworkController.h" +#include "NetworkService.h" #include @@ -9,8 +9,7 @@ #include "Base/LoggingService.h" #include "Base/Resources.h" -#include "MessageDialog.h" -#include "NetworkDataParser.h" +#include "NetworkResourceParserService.h" namespace { @@ -58,51 +57,34 @@ namespace } } -_NetworkController::_NetworkController() +NetworkService& NetworkService::getInstance() { - _serverAddress = GlobalSettings::getInstance().getStringState("settings.server", "alien-project.org"); + static NetworkService instance; + return instance; } -_NetworkController::~_NetworkController() -{ - GlobalSettings::getInstance().setStringState("settings.server", _serverAddress); - logout(); -} - -void _NetworkController::process() -{ - auto now = std::chrono::steady_clock::now(); - if (!_lastRefreshTime) { - _lastRefreshTime = now; - } - if (std::chrono::duration_cast(now - *_lastRefreshTime).count() >= RefreshInterval) { - _lastRefreshTime = now; - refreshLogin(); - } -} - -std::string _NetworkController::getServerAddress() const +std::string NetworkService::getServerAddress() const { return _serverAddress; } -void _NetworkController::setServerAddress(std::string const& value) +void NetworkService::setServerAddress(std::string const& value) { _serverAddress = value; logout(); } -std::optional _NetworkController::getLoggedInUserName() const +std::optional NetworkService::getLoggedInUserName() const { return _loggedInUserName; } -std::optional _NetworkController::getPassword() const +std::optional NetworkService::getPassword() const { return _password; } -bool _NetworkController::createUser(std::string const& userName, std::string const& password, std::string const& email) +bool NetworkService::createUser(std::string const& userName, std::string const& password, std::string const& email) { log(Priority::Important, "network: create user '" + userName + "'"); @@ -123,7 +105,7 @@ bool _NetworkController::createUser(std::string const& userName, std::string con } } -bool _NetworkController::activateUser(std::string const& userName, std::string const& password, UserInfo const& userInfo, std::string const& confirmationCode) +bool NetworkService::activateUser(std::string const& userName, std::string const& password, UserInfo const& userInfo, std::string const& confirmationCode) { log(Priority::Important, "network: activate user '" + userName + "'"); @@ -147,7 +129,7 @@ bool _NetworkController::activateUser(std::string const& userName, std::string c } } -bool _NetworkController::login(LoginErrorCode& errorCode, std::string const& userName, std::string const& password, UserInfo const& userInfo) +bool NetworkService::login(LoginErrorCode& errorCode, std::string const& userName, std::string const& password, UserInfo const& userInfo) { log(Priority::Important, "network: login user '" + userName + "'"); @@ -183,7 +165,7 @@ bool _NetworkController::login(LoginErrorCode& errorCode, std::string const& use } } -bool _NetworkController::logout() +bool NetworkService::logout() { log(Priority::Important, "network: logout"); bool result = true; @@ -209,7 +191,32 @@ bool _NetworkController::logout() return result; } -bool _NetworkController::deleteUser() +void NetworkService::shutdown() +{ + GlobalSettings::getInstance().setStringState("settings.server", _serverAddress); + logout(); +} + +void NetworkService::refreshLogin() +{ + if (_loggedInUserName && _password) { + log(Priority::Important, "network: refresh login"); + + httplib::SSLClient client(_serverAddress); + configureClient(client); + + httplib::Params params; + params.emplace("userName", *_loggedInUserName); + params.emplace("password", *_password); + + try { + executeRequest([&] { return client.Post("/alien-server/refreshlogin.php", params); }); + } catch (...) { + } + } +} + +bool NetworkService::deleteUser() { log(Priority::Important, "network: delete user '" + *_loggedInUserName + "'"); @@ -234,7 +241,7 @@ bool _NetworkController::deleteUser() } } -bool _NetworkController::resetPassword(std::string const& userName, std::string const& email) +bool NetworkService::resetPassword(std::string const& userName, std::string const& email) { log(Priority::Important, "network: reset password of user '" + userName + "'"); @@ -254,7 +261,7 @@ bool _NetworkController::resetPassword(std::string const& userName, std::string } } -bool _NetworkController::setNewPassword(std::string const& userName, std::string const& newPassword, std::string const& confirmationCode) +bool NetworkService::setNewPassword(std::string const& userName, std::string const& newPassword, std::string const& confirmationCode) { log(Priority::Important, "network: set new password for user '" + userName + "'"); @@ -275,7 +282,7 @@ bool _NetworkController::setNewPassword(std::string const& userName, std::string } } -bool _NetworkController::getRemoteSimulationList(std::vector& result, bool withRetry) const +bool NetworkService::getRemoteSimulationList(std::vector& result, bool withRetry) const { log(Priority::Important, "network: get simulation list"); @@ -291,7 +298,7 @@ bool _NetworkController::getRemoteSimulationList(std::vectorbody); boost::property_tree::ptree tree; boost::property_tree::read_json(stream, tree); - result = NetworkDataParser::decodeRemoteSimulationData(tree); + result = NetworkResourceParserService::decodeRemoteSimulationData(tree); return true; } catch (...) { logNetworkError(); @@ -299,7 +306,7 @@ bool _NetworkController::getRemoteSimulationList(std::vector& result, bool withRetry) const +bool NetworkService::getUserList(std::vector& result, bool withRetry) const { log(Priority::Important, "network: get user list"); @@ -314,8 +321,8 @@ bool _NetworkController::getUserList(std::vector& result, bool withRet boost::property_tree::ptree tree; boost::property_tree::read_json(stream, tree); result.clear(); - result = NetworkDataParser::decodeUserData(tree); - for (UserData& userData : result) { + result = NetworkResourceParserService::decodeUserData(tree); + for (UserTO& userData : result) { userData.timeSpent = userData.timeSpent * RefreshInterval / 60; } return true; @@ -325,7 +332,7 @@ bool _NetworkController::getUserList(std::vector& result, bool withRet } } -bool _NetworkController::getEmojiTypeBySimId(std::unordered_map& result) const +bool NetworkService::getEmojiTypeBySimId(std::unordered_map& result) const { log(Priority::Important, "network: get liked simulations"); @@ -354,7 +361,7 @@ bool _NetworkController::getEmojiTypeBySimId(std::unordered_map& result, std::string const& simId, int likeType) +bool NetworkService::getUserNamesForSimulationAndEmojiType(std::set& result, std::string const& simId, int likeType) { log(Priority::Important, "network: get user likes for simulation with id=" + simId + " and likeType=" + std::to_string(likeType)); @@ -383,7 +390,7 @@ bool _NetworkController::getUserNamesForSimulationAndEmojiType(std::set -#include "RemoteSimulationData.h" -#include "UserData.h" +#include "NetworkResourceRawTO.h" +#include "UserTO.h" #include "Definitions.h" using LoginErrorCode = int; @@ -18,13 +18,11 @@ struct UserInfo std::optional gpu; }; -class _NetworkController +class NetworkService { public: - _NetworkController(); - ~_NetworkController(); - - void process(); + static NetworkService& getInstance(); + NetworkService(NetworkService const&) = delete; std::string getServerAddress() const; void setServerAddress(std::string const& value); @@ -36,12 +34,14 @@ class _NetworkController bool login(LoginErrorCode& errorCode, std::string const& userName, std::string const& password, UserInfo const& userInfo); bool logout(); + void shutdown(); + void refreshLogin(); bool deleteUser(); bool resetPassword(std::string const& userName, std::string const& email); bool setNewPassword(std::string const& userName, std::string const& newPassword, std::string const& confirmationCode); - bool getRemoteSimulationList(std::vector& result, bool withRetry) const; - bool getUserList(std::vector& result, bool withRetry) const; + bool getRemoteSimulationList(std::vector& result, bool withRetry) const; + bool getUserList(std::vector& result, bool withRetry) const; bool getEmojiTypeBySimId(std::unordered_map& result) const; bool getUserNamesForSimulationAndEmojiType(std::set& result, std::string const& simId, int likeType); bool toggleLikeSimulation(std::string const& simId, int likeType); @@ -54,12 +54,13 @@ class _NetworkController std::string const& data, std::string const& settings, std::string const& statistics, - RemoteDataType type); + NetworkResourceType type); bool downloadSimulation(std::string& mainData, std::string& auxiliaryData, std::string& statistics, std::string const& simId); bool deleteSimulation(std::string const& simId); private: - void refreshLogin(); + NetworkService(); + ~NetworkService(); std::string _serverAddress; std::optional _loggedInUserName; diff --git a/source/Gui/UserData.h b/source/Network/UserTO.h similarity index 82% rename from source/Gui/UserData.h rename to source/Network/UserTO.h index 143c3c64a..4bb7fbd0e 100644 --- a/source/Gui/UserData.h +++ b/source/Network/UserTO.h @@ -1,7 +1,8 @@ #pragma once + #include -class UserData +class UserTO { public: std::string userName; @@ -13,7 +14,7 @@ class UserData int timeSpent; std::string gpu; - static int compareOnlineAndTimestamp(UserData const& left, UserData const& right) + static int compareOnlineAndTimestamp(UserTO const& left, UserTO const& right) { if (int result = static_cast(left.online) - static_cast(right.online)) { return result; diff --git a/source/NetworkTests/CMakeLists.txt b/source/NetworkTests/CMakeLists.txt new file mode 100644 index 000000000..659e2c157 --- /dev/null +++ b/source/NetworkTests/CMakeLists.txt @@ -0,0 +1,19 @@ +target_sources(NetworkTests +PUBLIC + NetworkResourceServiceTests.cpp + Testsuite.cpp) + +target_link_libraries(NetworkTests Base) +target_link_libraries(NetworkTests EngineInterface) +target_link_libraries(NetworkTests Network) + +target_link_libraries(NetworkTests Boost::boost) +target_link_libraries(NetworkTests OpenGL::GL OpenGL::GLU) +target_link_libraries(NetworkTests GLEW::GLEW) +target_link_libraries(NetworkTests glfw) +target_link_libraries(NetworkTests glad::glad) +target_link_libraries(NetworkTests GTest::GTest GTest::Main) + +if (MSVC) + target_compile_options(NetworkTests PRIVATE "/MP") +endif() diff --git a/source/NetworkTests/NetworkResourceServiceTests.cpp b/source/NetworkTests/NetworkResourceServiceTests.cpp new file mode 100644 index 000000000..30214f5e8 --- /dev/null +++ b/source/NetworkTests/NetworkResourceServiceTests.cpp @@ -0,0 +1,147 @@ +#include + +#include "Base/NumberGenerator.h" +#include "Network/NetworkResourceRawTO.h" +#include "Network/NetworkResourceService.h" + +#include "Network/NetworkResourceTreeTO.h" + +class NetworkResourceServiceTests : public ::testing::Test +{ +public: + NetworkResourceServiceTests() + {} + ~NetworkResourceServiceTests() = default; +}; + +TEST_F(NetworkResourceServiceTests, nameWithoutFolder) +{ + std::vector inputTOs; + auto inputTO = std::make_shared<_NetworkResourceRawTO>(); + inputTO->resourceName = "test"; + inputTOs.emplace_back(inputTO); + + auto outputTOs = NetworkResourceService::createTreeTOs(inputTOs, {}); + + ASSERT_EQ(1, outputTOs.size()); +} + +TEST_F(NetworkResourceServiceTests, nameWithFolder) +{ + std::vector inputTOs; + auto inputTO = std::make_shared<_NetworkResourceRawTO>(); + inputTO->resourceName = "folder/test"; + inputTOs.emplace_back(inputTO); + + auto outputTOs = NetworkResourceService::createTreeTOs(inputTOs, {}); + + ASSERT_EQ(2, outputTOs.size()); + { + auto outputTO = outputTOs.front(); + EXPECT_FALSE(outputTO->isLeaf()); + EXPECT_EQ(1, outputTO->folderNames.size()); + EXPECT_EQ(std::string("folder"), outputTO->folderNames.front()); + } + { + auto outputTO = outputTOs.back(); + EXPECT_TRUE(outputTO->isLeaf()); + EXPECT_EQ(1, outputTO->folderNames.size()); + EXPECT_EQ(std::string("folder"), outputTO->folderNames.front()); + EXPECT_EQ(std::string("test"), outputTO->getLeaf().leafName); + } +} + +TEST_F(NetworkResourceServiceTests, nameWithTwoFolders) +{ + std::vector inputTOs; + auto inputTO = std::make_shared<_NetworkResourceRawTO>(); + inputTO->resourceName = "folder1/folder2/test"; + inputTOs.emplace_back(inputTO); + + auto outputTOs = NetworkResourceService::createTreeTOs(inputTOs, {}); + + ASSERT_EQ(3, outputTOs.size()); + { + auto outputTO = outputTOs.at(0); + EXPECT_FALSE(outputTO->isLeaf()); + EXPECT_EQ(1, outputTO->folderNames.size()); + EXPECT_EQ(std::string("folder1"), outputTO->folderNames.at(0)); + } + { + auto outputTO = outputTOs.at(1); + EXPECT_FALSE(outputTO->isLeaf()); + EXPECT_EQ(2, outputTO->folderNames.size()); + EXPECT_EQ(std::string("folder1"), outputTO->folderNames.at(0)); + EXPECT_EQ(std::string("folder2"), outputTO->folderNames.at(1)); + } + { + auto outputTO = outputTOs.at(2); + EXPECT_TRUE(outputTO->isLeaf()); + EXPECT_EQ(2, outputTO->folderNames.size()); + EXPECT_EQ(std::string("folder1"), outputTO->folderNames.at(0)); + EXPECT_EQ(std::string("folder2"), outputTO->folderNames.at(1)); + EXPECT_EQ(std::string("test"), outputTO->getLeaf().leafName); + } +} + + +TEST_F(NetworkResourceServiceTests, twoNamesWithTwoFolders) +{ + std::vector inputTOs; + { + auto inputTO = std::make_shared<_NetworkResourceRawTO>(); + inputTO->resourceName = "A/B/C"; + inputTOs.emplace_back(inputTO); + } + { + auto inputTO = std::make_shared<_NetworkResourceRawTO>(); + inputTO->resourceName = "X/Y/Z"; + inputTOs.emplace_back(inputTO); + } + + auto outputTOs = NetworkResourceService::createTreeTOs(inputTOs, {}); + + ASSERT_EQ(6, outputTOs.size()); + { + auto outputTO = outputTOs.at(0); + EXPECT_FALSE(outputTO->isLeaf()); + EXPECT_EQ(1, outputTO->folderNames.size()); + EXPECT_EQ(std::string("A"), outputTO->folderNames.at(0)); + } + { + auto outputTO = outputTOs.at(1); + EXPECT_FALSE(outputTO->isLeaf()); + EXPECT_EQ(2, outputTO->folderNames.size()); + EXPECT_EQ(std::string("A"), outputTO->folderNames.at(0)); + EXPECT_EQ(std::string("B"), outputTO->folderNames.at(1)); + } + { + auto outputTO = outputTOs.at(2); + EXPECT_TRUE(outputTO->isLeaf()); + EXPECT_EQ(2, outputTO->folderNames.size()); + EXPECT_EQ(std::string("A"), outputTO->folderNames.at(0)); + EXPECT_EQ(std::string("B"), outputTO->folderNames.at(1)); + EXPECT_EQ(std::string("C"), outputTO->getLeaf().leafName); + } + { + auto outputTO = outputTOs.at(3); + EXPECT_FALSE(outputTO->isLeaf()); + EXPECT_EQ(1, outputTO->folderNames.size()); + EXPECT_EQ(std::string("X"), outputTO->folderNames.at(0)); + } + { + auto outputTO = outputTOs.at(4); + EXPECT_FALSE(outputTO->isLeaf()); + EXPECT_EQ(2, outputTO->folderNames.size()); + EXPECT_EQ(std::string("X"), outputTO->folderNames.at(0)); + EXPECT_EQ(std::string("Y"), outputTO->folderNames.at(1)); + } + { + auto outputTO = outputTOs.at(5); + EXPECT_TRUE(outputTO->isLeaf()); + EXPECT_EQ(2, outputTO->folderNames.size()); + EXPECT_EQ(std::string("X"), outputTO->folderNames.at(0)); + EXPECT_EQ(std::string("Y"), outputTO->folderNames.at(1)); + EXPECT_EQ(std::string("Z"), outputTO->getLeaf().leafName); + } +} diff --git a/source/NetworkTests/Testsuite.cpp b/source/NetworkTests/Testsuite.cpp new file mode 100644 index 000000000..9bb465e02 --- /dev/null +++ b/source/NetworkTests/Testsuite.cpp @@ -0,0 +1,7 @@ +#include + +int main(int argc, char** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/vcpkg.json b/vcpkg.json index 21ee190ec..6a23ddab1 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,6 +1,6 @@ { "name": "alien", - "version": "4.5.1", + "version": "4.6.0", "dependencies": [ { "name": "glew",