From 7c626896daad13cf9ffbc58da5430ceb7b17f6d7 Mon Sep 17 00:00:00 2001 From: GhostofCookie Date: Wed, 23 Apr 2025 13:19:57 -0400 Subject: [PATCH 1/5] Starting to improve how modules are created/imported. --- CMakeLists.txt | 73 ++++---- cmake/add_modules.cmake | 1 + include/flow/ui/windows/NewModuleWindow.hpp | 26 +++ programs/editor/CMakeLists.txt | 34 ++-- src/Editor.cpp | 1 + src/windows/ModuleManagerWindow.cpp | 77 +++++++- src/windows/NewModuleWindow.cpp | 190 ++++++++++++++++++++ templates/ModuleCMakeLists.txt.in | 23 +++ templates/register.cpp.in | 26 +++ 9 files changed, 388 insertions(+), 63 deletions(-) create mode 100644 cmake/add_modules.cmake create mode 100644 include/flow/ui/windows/NewModuleWindow.hpp create mode 100644 src/windows/NewModuleWindow.cpp create mode 100644 templates/ModuleCMakeLists.txt.in create mode 100644 templates/register.cpp.in diff --git a/CMakeLists.txt b/CMakeLists.txt index 457af49..aa05d15 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,11 +4,21 @@ if(NOT PROJECT_NAME) add_compile_definitions(FLOW_UI_EXPORT) endif() -project(flow-ui VERSION 1.0.1 LANGUAGES CXX) +project(Flow VERSION 1.0.1 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib") +foreach(CONFIG ${CMAKE_CONFIGURATION_TYPES}) + string(TOUPPER ${CONFIG} CONFIG) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${CONFIG} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${CONFIG} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${CONFIG} ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}) +endforeach(CONFIG CMAKE_CONFIGURATION_TYPES) + if(APPLE) enable_language(OBJC) elseif(MSVC) @@ -30,7 +40,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake") include(cmake/CPM.cmake) CPMAddPackage("gh:InFlowStructure/flow-core@1.2.0") -CPMAddPackage("gh:btzy/nativefiledialog-extended@1.2.1") +CPMAddPackage("gh:btzy/nativefiledialog-extended#29e3bcb") FetchContent_Declare( imgui @@ -88,7 +98,7 @@ list(APPEND flow-ui_HEADERS ${flow-ui_WINDOW_HEADERS} ) -add_library(${PROJECT_NAME} SHARED +add_library(flow-ui SHARED # Main source files src/Config.cpp src/Core.cpp @@ -107,6 +117,7 @@ add_library(${PROJECT_NAME} SHARED # Window source files src/windows/GraphWindow.cpp src/windows/ModuleManagerWindow.cpp + src/windows/NewModuleWindow.cpp src/windows/NodeExplorerWindow.cpp src/windows/PropertyWindow.cpp src/windows/ShortcutsWindow.cpp @@ -124,14 +135,14 @@ add_library(${PROJECT_NAME} SHARED ${flow-ui_HEADERS} ) -add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) +add_library(flow-ui::flow-ui ALIAS flow-ui) if(MSVC) add_compile_definitions(_CRT_SECURE_NO_WARNINGS) - target_compile_options(${PROJECT_NAME} PRIVATE /W4) + target_compile_options(flow-ui PRIVATE /W4) endif() -target_include_directories(${PROJECT_NAME} +target_include_directories(flow-ui PUBLIC $ $ @@ -145,57 +156,33 @@ target_include_directories(${PROJECT_NAME} ${PROJECT_SOURCE_DIR}/include/flow/ui/widgets ) -target_link_libraries(${PROJECT_NAME} PUBLIC +target_link_libraries(flow-ui PUBLIC flow-core::flow-core nlohmann_json::nlohmann_json spdlog::spdlog ) -target_link_libraries(${PROJECT_NAME} PRIVATE +target_link_libraries(flow-ui PRIVATE imgui imgui_node_editor hello_imgui nfd ) -target_compile_definitions(${PROJECT_NAME} PUBLIC IMGUI_DEFINE_MATH_OPERATORS) +target_compile_definitions(flow-ui PUBLIC IMGUI_DEFINE_MATH_OPERATORS) add_subdirectory(programs/editor) -# ----------------------------------------------------------------------------- -# Install -# ----------------------------------------------------------------------------- +install(TARGETS flow-ui flow-core + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin +) + +install(DIRECTORY ${CMAKE_SOURCE_DIR}/include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) +install(DIRECTORY ${flow-core_SOURCE_DIR}/include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) +install(DIRECTORY ${CPM_PACKAGE_json_SOURCE_DIR}/include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) +install(DIRECTORY ${CPM_PACKAGE_thread-pool_SOURCE_DIR}/include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) -if(flow-ui_INSTALL) - set(export_destination "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}") - - install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) - install(TARGETS ${PROJECT_NAME} imgui hello_imgui imgui_node_editor - EXPORT ${PROJECT_NAME}Targets - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} - ) - - install(EXPORT ${PROJECT_NAME}Targets - DESTINATION ${export_destination} - NAMESPACE ${PROJECT_NAME}:: - FILE "${PROJECT_NAME}Targets.cmake") - - include(CMakePackageConfigHelpers) - configure_package_config_file( - "${CMAKE_CURRENT_LIST_DIR}/cmake/${PROJECT_NAME}Config.cmake.in" - "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" - INSTALL_DESTINATION ${export_destination} - ) - - install( - FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" - DESTINATION ${export_destination} - ) - - write_basic_package_version_file(${PROJECT_NAME}ConfigVersion.cmake COMPATIBILITY SameMajorVersion) -endif() # ----------------------------------------------------------------------------- # Examples diff --git a/cmake/add_modules.cmake b/cmake/add_modules.cmake new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/cmake/add_modules.cmake @@ -0,0 +1 @@ + diff --git a/include/flow/ui/windows/NewModuleWindow.hpp b/include/flow/ui/windows/NewModuleWindow.hpp new file mode 100644 index 0000000..e09ac84 --- /dev/null +++ b/include/flow/ui/windows/NewModuleWindow.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "flow/ui/Core.hpp" +#include "flow/ui/Window.hpp" + +FLOW_UI_NAMESPACE_START + +class NewModuleWindow : public Window +{ + public: + NewModuleWindow(); + virtual ~NewModuleWindow() = default; + + virtual void Draw() override; + + public: + static inline const std::string Name = "Create New Module"; + + private: + std::string _name; + std::string _version; + std::string _author; + std::string _description; +}; + +FLOW_UI_NAMESPACE_END diff --git a/programs/editor/CMakeLists.txt b/programs/editor/CMakeLists.txt index 7bbbd4e..24188d8 100644 --- a/programs/editor/CMakeLists.txt +++ b/programs/editor/CMakeLists.txt @@ -1,6 +1,4 @@ -cmake_minimum_required(VERSION 3.10) - -project(FlowEditor VERSION 1.0.1 LANGUAGES CXX) +cmake_minimum_required(VERSION 3.21) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -19,22 +17,26 @@ if(MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /utf-8") endif() -hello_imgui_add_app(${PROJECT_NAME} - src/main.cpp +if(PACK) + set(INSTALL_DIR bin) + set(LIB_DIR bin/lib) +else(PACK) + set(INSTALL_DIR ${PROJECT_BINARY_DIR}/bin) + set(LIB_DIR ${PROJECT_BINARY_DIR}/lib) +endif(PACK) - ASSETS_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/assets/ +hello_imgui_add_app(FlowEditor + src/main.cpp ) -target_link_libraries(${PROJECT_NAME} PUBLIC +target_link_libraries(FlowEditor PUBLIC flow-ui::flow-ui cxxopts - imgui_node_editor ) -if(MSVC) - add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - $ - $ - COMMAND_EXPAND_LISTS - ) -endif() +add_custom_command(TARGET FlowEditor POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_SOURCE_DIR}/templates + $/templates +) + +install(DIRECTORY ${CMAKE_SOURCE_DIR}/templates DESTINATION ${CMAKE_INSTALL_PREFIX}) diff --git a/src/Editor.cpp b/src/Editor.cpp index 385cd06..79b61a4 100644 --- a/src/Editor.cpp +++ b/src/Editor.cpp @@ -155,6 +155,7 @@ void Editor::Init(const std::string& initial_file) AddDockspace(PropertyDockspace, DefaultDockspace, 0.25f, DockspaceSplitDirection::Left); AddDockspace("PropertySubSpace", PropertyDockspace, 0.5f, DockspaceSplitDirection::Down); + AddDockspace("ToolbarSpace", DefaultDockspace, 0.1f, DockspaceSplitDirection::Up); AddDockspace("MiscSpace", DefaultDockspace, 0.25f, DockspaceSplitDirection::Down); auto property_window = std::make_shared(_env); diff --git a/src/windows/ModuleManagerWindow.cpp b/src/windows/ModuleManagerWindow.cpp index 7949530..03b56be 100644 --- a/src/windows/ModuleManagerWindow.cpp +++ b/src/windows/ModuleManagerWindow.cpp @@ -1,10 +1,17 @@ #include "ModuleManagerWindow.hpp" +#include "FileExplorer.hpp" #include "InputField.hpp" +#include "NewModuleWindow.hpp" #include "Text.hpp" #include "Widget.hpp" +#include "utilities/Conversions.hpp" #include +#include + +#include +#include FLOW_UI_NAMESPACE_START @@ -26,13 +33,30 @@ class ModuleView : public Widget virtual void operator()() noexcept { - const std::string& name = _name.filename().replace_extension("").string(); + const std::string& name = _name.filename().replace_extension("").string(); + const std::string& version = "0.0.0"; + const std::string& author = "Cisco Systems, Inc."; + + ImGui::TableNextColumn(); _enabled(); ImGui::TableNextColumn(); + + ImGui::BeginHorizontal(("module_" + name).c_str()); widgets::Text{name}.SetFontSize(20.f)(); + auto posX = (ImGui::GetCursorPosX() + ImGui::GetColumnWidth() - ImGui::CalcTextSize(author.c_str()).x - + ImGui::GetScrollX() - 2 * ImGui::GetStyle().ItemSpacing.x); + if (posX > ImGui::GetCursorPosX()) ImGui::SetCursorPosX(posX); + + ImGui::BeginVertical("version/author"); + constexpr Colour version_author_colour{150, 150, 150}; + widgets::Text{"Version: " + version}.SetFontSize(20.f).SetColour(version_author_colour)(); + widgets::Text{author}.SetFontSize(18.f).SetColour(version_author_colour)(); + ImGui::EndVertical(); + ImGui::EndHorizontal(); + if (auto data = _enabled.GetData()) { if (_enabled.GetValue()) @@ -60,22 +84,65 @@ ModuleManagerWindow::ModuleManagerWindow(std::shared_ptr env, const std::fi void ModuleManagerWindow::Draw() { + ImGui::BeginHorizontal("ModuleButtons"); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10, 10)); + ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered)); + + if (ImGui::Button("+ New Module")) + { + ImGui::OpenPopup("Create Module"); + } + + if (ImGui::Button("Import Module")) + { + const auto filename = FileExplorer::Load(FileExplorer::GetDocumentsPath(), "Flow Modules", "so,dll,dylib"); + + try + { + _env->LoadModule(filename); + } + catch (const std::exception& e) + { + SPDLOG_ERROR("Caught exception while loading module {}: {}", filename.string(), e.what()); + } + } + + ImGui::PopStyleColor(); + ImGui::PopStyleVar(); + + ImVec2 center = ImGui::GetMainViewport()->GetCenter(); + ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + + if (ImGui::BeginPopupModal("Create Module", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize)) + { + NewModuleWindow().Draw(); + ImGui::EndPopup(); + } + ImGui::EndHorizontal(); + if (!std::filesystem::exists(_modules_path) || std::filesystem::is_empty(_modules_path)) { return Window::Draw(); } + ImGui::BeginChild("ModuleList"); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.f); ImGui::PushStyleVar(ImGuiStyleVar_IndentSpacing, 10.f); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(1.f, 1.f)); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.f, 10.f)); ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(10.f, 10.f)); - ImGui::BeginTable((_name + "##list").c_str(), 2, - ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter); + ImGui::BeginTable((_name + "##list").c_str(), 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter); + + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); for (const auto& module : std::filesystem::directory_iterator(_modules_path)) { - ImGui::TableNextColumn(); + if (std::filesystem::is_directory(module)) + { + continue; + } const auto& module_name = module.path().string(); if (!_widgets.contains(module_name)) @@ -87,6 +154,8 @@ void ModuleManagerWindow::Draw() ImGui::EndTable(); ImGui::PopStyleVar(5); + + ImGui::EndChild(); } FLOW_UI_NAMESPACE_END diff --git a/src/windows/NewModuleWindow.cpp b/src/windows/NewModuleWindow.cpp new file mode 100644 index 0000000..e18d6a8 --- /dev/null +++ b/src/windows/NewModuleWindow.cpp @@ -0,0 +1,190 @@ +#include "NewModuleWindow.hpp" + +#include "FileExplorer.hpp" +#include "widgets/InputField.hpp" +#include "widgets/Table.hpp" +#include "widgets/Text.hpp" + +#include + +#include +#include + +FLOW_UI_NAMESPACE_START + +std::string replace_all(std::string str, const std::string& from, const std::string& to) +{ + std::size_t start_pos = 0; + while ((start_pos = str.find(from)) != std::string::npos) + { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); + } + + return str; +} + +void GenerateProjectFiles(std::filesystem::path project_dir, const std::string& name, const std::string version, + const std::vector& dependencies) +{ + if (std::filesystem::exists(project_dir)) + { + return; + } + + std::filesystem::create_directories(project_dir); + std::filesystem::create_directories(project_dir / "src"); + std::filesystem::create_directories(project_dir / "include"); + + std::string namespace_str = name; + namespace_str = replace_all(namespace_str, "-", "_"); + namespace_str = replace_all(namespace_str, " ", "_"); + + std::string upper_case_ns_str = namespace_str; + + const auto& to_upper = [](unsigned char c) { return static_cast(std::toupper(c)); }; + std::transform(upper_case_ns_str.begin(), upper_case_ns_str.end(), upper_case_ns_str.begin(), to_upper); + + const std::string export_str = upper_case_ns_str + "_EXPORT"; + const std::string api_str = upper_case_ns_str + "_API"; + + // CMakeLists.txt + { + const std::string dependencies_str = std::accumulate( + dependencies.begin(), dependencies.end(), std::string(""), [](const std::string& a, const std::string& b) { + return a.empty() ? "${" + b + "}" : a + " " + "${" + b + "}"; + }); + + const std::string find_lib_str = "find_library({{lib}} NAMES {{lib}} REQUIRED)"; + const std::string find_libs_str = std::accumulate(dependencies.begin(), dependencies.end(), std::string(""), + [&](const std::string& a, const std::string& b) { + return a + replace_all(find_lib_str, "{{lib}}", b) + "\n"; + }); + + std::ifstream cmake_lists_template_fs(FileExplorer::GetExecutablePath() / "templates" / + "ModuleCMakeLists.txt.in"); + std::stringstream buffer; + buffer << cmake_lists_template_fs.rdbuf(); + + std::string cmake_lists_template = buffer.str(); + cmake_lists_template = replace_all(cmake_lists_template, "{{name}}", name); + cmake_lists_template = replace_all(cmake_lists_template, "{{version}}", version); + cmake_lists_template = replace_all(cmake_lists_template, "{{find_libs}}", find_libs_str); + cmake_lists_template = replace_all(cmake_lists_template, "{{dependencies}}", dependencies_str); + cmake_lists_template = replace_all(cmake_lists_template, "{{export}}", export_str); + cmake_lists_template = replace_all(cmake_lists_template, "{{flow_dir}}", + replace_all(FileExplorer::GetExecutablePath().string(), "\\", "/")); + + std::ofstream cmake_lists_fs(project_dir / "CMakeLists.txt"); + cmake_lists_fs << cmake_lists_template; + } + + // register.cpp + { + std::ifstream register_source_template_fs(FileExplorer::GetExecutablePath() / "templates" / "register.cpp.in"); + std::stringstream buffer; + buffer << register_source_template_fs.rdbuf(); + + std::string register_source_template = buffer.str(); + register_source_template = replace_all(register_source_template, "{{namespace}}", namespace_str); + register_source_template = replace_all(register_source_template, "{{export}}", export_str); + register_source_template = replace_all(register_source_template, "{{api}}", api_str); + + std::ofstream register_source_fs(project_dir / "src" / "register.cpp"); + register_source_fs << register_source_template; + } +} + +void BuildProject(std::filesystem::path project_dir) +{ + const std::filesystem::path build_dir = project_dir / "build"; + + std::string configure_cmd = "cmake -S {{project_dir}} -B {{build_dir}}"; + configure_cmd = replace_all(configure_cmd, "{{project_dir}}", project_dir.string()); + configure_cmd = replace_all(configure_cmd, "{{build_dir}}", build_dir.string()); + + std::string build_cmd = "cmake --build {{build_dir}} -j"; + build_cmd = replace_all(build_cmd, "{{build_dir}}", build_dir.string()); + + std::system(configure_cmd.c_str()); + std::system(build_cmd.c_str()); +} + +NewModuleWindow::NewModuleWindow() : Window(Name) {} + +void NewModuleWindow::Draw() +try +{ + static auto name_input = std::make_shared>("name", _name); + static auto version_input = std::make_shared>("version", _version); + static auto author_input = std::make_shared>("author", _author); + static auto description_input = std::make_shared>("description", _description); + static auto dependencies = std::make_shared("dependencies", 2); + + static auto flow_core_dep = std::make_shared>("flow-core", true); + if (flow_core_dep.use_count() == 1) + { + dependencies->AddEntry(flow_core_dep); + dependencies->AddEntry(std::make_shared("flow-core")); + } + + static auto flow_ui_dep = std::make_shared>("flow-ui", false); + if (flow_ui_dep.use_count() == 1) + { + dependencies->AddEntry(flow_ui_dep); + dependencies->AddEntry(std::make_shared("flow-ui")); + } + + widgets::Table new_module_form("new_module_form", 2); + new_module_form.AddEntry(std::make_shared("Name")); + new_module_form.AddEntry(name_input); + new_module_form.AddEntry(std::make_shared("Version")); + new_module_form.AddEntry(version_input); + new_module_form.AddEntry(std::make_shared("Author")); + new_module_form.AddEntry(author_input); + new_module_form.AddEntry(std::make_shared("Description")); + new_module_form.AddEntry(description_input); + new_module_form.AddEntry(std::make_shared("Dependencies")); + new_module_form.AddEntry(dependencies); + + new_module_form(); + + if (auto _ = name_input->GetData()) + { + _name = name_input->GetValue(); + } + + ImGui::BeginHorizontal("buttons"); + ImGui::Spring(); + if (ImGui::Button("Create")) + { + const auto& project_dir = FileExplorer::GetDocumentsPath() / _name; + + GenerateProjectFiles(project_dir, _name, _version, {"flow-core", "flow-ui"}); + BuildProject(project_dir); + + try + { + // std::filesystem::copy_file(project_dir / "build" / "Debug" / "cmath.dll", _modules_path / "cmath.dll", + // std::filesystem::copy_options::overwrite_existing); + } + catch (...) + { + } + + ImGui::CloseCurrentPopup(); + } + + ImGui::SetItemDefaultFocus(); + if (ImGui::Button("Cancel")) + { + ImGui::CloseCurrentPopup(); + } + ImGui::EndHorizontal(); +} +catch (...) +{ + Window::Draw(); +} + +FLOW_UI_NAMESPACE_END diff --git a/templates/ModuleCMakeLists.txt.in b/templates/ModuleCMakeLists.txt.in new file mode 100644 index 0000000..2666c30 --- /dev/null +++ b/templates/ModuleCMakeLists.txt.in @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.10) + +project({{name}} VERSION {{version}} LANGUAGES CXX) + +{{find_libs}} + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +if(APPLE) + enable_language(OBJC) +endif() + +add_library(${PROJECT_NAME} SHARED src/register.cpp) +target_compile_definitions(${PROJECT_NAME} PRIVATE {{export}}) + +if(MSVC) + set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS TRUE) + target_compile_options(${PROJECT_NAME} PRIVATE /W4) +endif() + +target_link_libraries(${PROJECT_NAME} PUBLIC {{dependencies}}) +target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include "{{flow_dir}}/include") diff --git a/templates/register.cpp.in b/templates/register.cpp.in new file mode 100644 index 0000000..1e86535 --- /dev/null +++ b/templates/register.cpp.in @@ -0,0 +1,26 @@ +#include +#include + +#ifdef FLOW_WINDOWS +#ifdef {{export}} +#define {{api}} __declspec(dllexport) FLOW_CORE_CALL +#else +#define {{api}} __declspec(dllimport) FLOW_CORE_CALL +#endif +#else +#define {{api}} +#endif + +extern "C" +{ +namespace {{namespace}} +{ + void {{api}} RegisterModule(std::shared_ptr factory) + { + } + + void {{api}} UnregisterModule(std::shared_ptr factory) + { + } +} +} From 67a968070dac43d139838ca4815a21efebd4bfe0 Mon Sep 17 00:00:00 2001 From: GhostofCookie Date: Thu, 24 Apr 2025 11:14:58 -0400 Subject: [PATCH 2/5] Modules getting better. --- include/flow/ui/widgets/Table.hpp | 2 + .../flow/ui/windows/ModuleManagerWindow.hpp | 2 + include/flow/ui/windows/NewModuleWindow.hpp | 16 +- src/windows/ModuleInfo.hpp | 21 +++ src/windows/ModuleManagerWindow.cpp | 117 +++++++++++--- src/windows/NewModuleWindow.cpp | 144 ++++++++++++------ templates/ModuleCMakeLists.txt.in | 2 +- 7 files changed, 231 insertions(+), 73 deletions(-) create mode 100644 src/windows/ModuleInfo.hpp diff --git a/include/flow/ui/widgets/Table.hpp b/include/flow/ui/widgets/Table.hpp index e55fe50..27582ee 100644 --- a/include/flow/ui/widgets/Table.hpp +++ b/include/flow/ui/widgets/Table.hpp @@ -36,6 +36,8 @@ class Table : public Widget */ void AddEntry(std::shared_ptr widget); + const std::shared_ptr& GetEntry(std::size_t i) const { return _widgets.at(i); } + private: std::string _name; std::size_t _columns = 0; diff --git a/include/flow/ui/windows/ModuleManagerWindow.hpp b/include/flow/ui/windows/ModuleManagerWindow.hpp index 847bd35..c3bf6eb 100644 --- a/include/flow/ui/windows/ModuleManagerWindow.hpp +++ b/include/flow/ui/windows/ModuleManagerWindow.hpp @@ -3,6 +3,7 @@ #include "flow/ui/Core.hpp" #include "flow/ui/Widget.hpp" #include "flow/ui/Window.hpp" +#include "flow/ui/windows/NewModuleWindow.hpp" #include @@ -36,6 +37,7 @@ class ModuleManagerWindow : public Window std::shared_ptr _env; std::filesystem::path _modules_path; std::map> _widgets; + std::unique_ptr _new_module_window; }; FLOW_UI_NAMESPACE_END diff --git a/include/flow/ui/windows/NewModuleWindow.hpp b/include/flow/ui/windows/NewModuleWindow.hpp index e09ac84..f7626eb 100644 --- a/include/flow/ui/windows/NewModuleWindow.hpp +++ b/include/flow/ui/windows/NewModuleWindow.hpp @@ -2,6 +2,8 @@ #include "flow/ui/Core.hpp" #include "flow/ui/Window.hpp" +#include "widgets/InputField.hpp" +#include "widgets/Table.hpp" FLOW_UI_NAMESPACE_START @@ -11,16 +13,20 @@ class NewModuleWindow : public Window NewModuleWindow(); virtual ~NewModuleWindow() = default; - virtual void Draw() override; + bool DrawAndCreate(); public: static inline const std::string Name = "Create New Module"; private: - std::string _name; - std::string _version; - std::string _author; - std::string _description; + void Clear(); + + private: + std::shared_ptr> name_input; + std::shared_ptr> version_input; + std::shared_ptr> author_input; + std::shared_ptr> description_input; + std::shared_ptr dependencies; }; FLOW_UI_NAMESPACE_END diff --git a/src/windows/ModuleInfo.hpp b/src/windows/ModuleInfo.hpp new file mode 100644 index 0000000..bcc4ade --- /dev/null +++ b/src/windows/ModuleInfo.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "Core.hpp" + +#include + +#include + +FLOW_UI_NAMESPACE_START + +struct ModuleInfo +{ + std::string Name; + std::string Version; + std::string Author; + std::string Description; +}; + +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(ModuleInfo, Name, Version, Author, Description); + +FLOW_UI_NAMESPACE_END diff --git a/src/windows/ModuleManagerWindow.cpp b/src/windows/ModuleManagerWindow.cpp index 03b56be..3cb9ad2 100644 --- a/src/windows/ModuleManagerWindow.cpp +++ b/src/windows/ModuleManagerWindow.cpp @@ -2,7 +2,7 @@ #include "FileExplorer.hpp" #include "InputField.hpp" -#include "NewModuleWindow.hpp" +#include "ModuleInfo.hpp" #include "Text.hpp" #include "Widget.hpp" #include "utilities/Conversions.hpp" @@ -15,27 +15,73 @@ FLOW_UI_NAMESPACE_START +const std::string module_file_extension = "flowmod"; +#ifdef FLOW_WINDOWS +const std::string module_binary_extension = ".dll"; +#elif FLOW_APPLE +const std::string module_binary_extension = ".dylib"; +#else +const std::string module_binary_extension = ".so"; +#endif + class ModuleView : public Widget { public: - ModuleView(const std::filesystem::path& name, std::shared_ptr env) - : _name(name), _env(std::move(env)), _enabled(name.filename().replace_extension("").string(), true) + ModuleView(const std::filesystem::path& name, std::shared_ptr env, const ModuleInfo& info) + : _binary_path(name), _env(std::move(env)), _info(info), + _enabled(name.filename().replace_extension("").string(), true) + { + if (_enabled.GetValue()) + { + _env->LoadModule(_binary_path); + } + else + { + _env->UnloadModule(_binary_path); + } + } + + ModuleView(const std::filesystem::path& dir, std::shared_ptr env) + : _env(std::move(env)), _enabled(dir.filename().replace_extension("").string(), true) { + std::filesystem::path module_path; + for (const auto& entry : std::filesystem::recursive_directory_iterator(dir)) + { + if (!std::filesystem::is_regular_file(entry)) + { + continue; + } + + if (entry.path().extension() == "." + module_file_extension) + { + module_path = entry; + } + + if (entry.path().extension() == module_binary_extension) + { + _binary_path = entry; + } + } + + std::ifstream module_fs(module_path); + json module_j = json::parse(module_fs); + _info = module_j; + if (_enabled.GetValue()) { - _env->LoadModule(_name); + _env->LoadModule(_binary_path); } else { - _env->UnloadModule(_name); + _env->UnloadModule(_binary_path); } } virtual void operator()() noexcept { - const std::string& name = _name.filename().replace_extension("").string(); - const std::string& version = "0.0.0"; - const std::string& author = "Cisco Systems, Inc."; + const std::string& name = _binary_path.filename().replace_extension("").string(); + const std::string& version = _info.Version; + const std::string& author = _info.Author; ImGui::TableNextColumn(); @@ -46,8 +92,12 @@ class ModuleView : public Widget ImGui::BeginHorizontal(("module_" + name).c_str()); widgets::Text{name}.SetFontSize(20.f)(); - auto posX = (ImGui::GetCursorPosX() + ImGui::GetColumnWidth() - ImGui::CalcTextSize(author.c_str()).x - - ImGui::GetScrollX() - 2 * ImGui::GetStyle().ItemSpacing.x); + auto posX = + (ImGui::GetCursorPosX() + ImGui::GetColumnWidth() - + ImGui::CalcTextSize(author.length() > ("Version: " + version).length() ? author.c_str() + : ("Version: " + version).c_str()) + .x + + -ImGui::GetScrollX() - 10 * ImGui::GetStyle().ItemSpacing.x); if (posX > ImGui::GetCursorPosX()) ImGui::SetCursorPosX(posX); ImGui::BeginVertical("version/author"); @@ -61,23 +111,25 @@ class ModuleView : public Widget { if (_enabled.GetValue()) { - _env->LoadModule(_name); + _env->LoadModule(_binary_path); } else { - _env->UnloadModule(_name); + _env->UnloadModule(_binary_path); } } } private: - std::filesystem::path _name; + std::filesystem::path _binary_path; std::shared_ptr _env; + ModuleInfo _info; widgets::Input _enabled; }; ModuleManagerWindow::ModuleManagerWindow(std::shared_ptr env, const std::filesystem::path& modules_path) - : Window("Module Manager"), _env(std::move(env)), _modules_path(modules_path) + : Window("Module Manager"), _env(std::move(env)), _modules_path(modules_path), + _new_module_window(std::make_unique()) { std::filesystem::create_directory(_modules_path); } @@ -95,11 +147,36 @@ void ModuleManagerWindow::Draw() if (ImGui::Button("Import Module")) { - const auto filename = FileExplorer::Load(FileExplorer::GetDocumentsPath(), "Flow Modules", "so,dll,dylib"); + std::filesystem::create_directory(_modules_path); + + const auto filename = + FileExplorer::Load(FileExplorer::GetDocumentsPath(), "Flow Modules (flowmod)", module_file_extension); try { - _env->LoadModule(filename); + std::ifstream module_fs(filename); + ModuleInfo module_info = json::parse(module_fs); + + const std::string module_file_name = module_info.Name + module_binary_extension; + std::filesystem::path module_binary_path; + for (const auto& entry : std::filesystem::recursive_directory_iterator(filename.parent_path())) + { + if (!std::filesystem::is_regular_file(entry) || entry.path().filename() != module_file_name) + { + continue; + } + + module_binary_path = entry; + break; + } + + std::filesystem::path module_path = _modules_path / module_info.Name; + std::filesystem::create_directory(module_path); + + std::filesystem::copy_file(filename, module_path / filename.filename(), + std::filesystem::copy_options::overwrite_existing); + std::filesystem::copy_file(module_binary_path, module_path / module_binary_path.filename(), + std::filesystem::copy_options::overwrite_existing); } catch (const std::exception& e) { @@ -115,7 +192,11 @@ void ModuleManagerWindow::Draw() if (ImGui::BeginPopupModal("Create Module", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize)) { - NewModuleWindow().Draw(); + if (_new_module_window->DrawAndCreate()) + { + _new_module_window.reset(new NewModuleWindow()); + } + ImGui::EndPopup(); } ImGui::EndHorizontal(); @@ -139,7 +220,7 @@ void ModuleManagerWindow::Draw() for (const auto& module : std::filesystem::directory_iterator(_modules_path)) { - if (std::filesystem::is_directory(module)) + if (!std::filesystem::is_directory(module)) { continue; } diff --git a/src/windows/NewModuleWindow.cpp b/src/windows/NewModuleWindow.cpp index e18d6a8..7414833 100644 --- a/src/windows/NewModuleWindow.cpp +++ b/src/windows/NewModuleWindow.cpp @@ -1,14 +1,16 @@ #include "NewModuleWindow.hpp" #include "FileExplorer.hpp" -#include "widgets/InputField.hpp" -#include "widgets/Table.hpp" +#include "ModuleInfo.hpp" #include "widgets/Text.hpp" #include #include #include +#include + +using namespace std::string_literals; FLOW_UI_NAMESPACE_START @@ -24,7 +26,7 @@ std::string replace_all(std::string str, const std::string& from, const std::str return str; } -void GenerateProjectFiles(std::filesystem::path project_dir, const std::string& name, const std::string version, +void GenerateProjectFiles(std::filesystem::path project_dir, const ModuleInfo& info, const std::vector& dependencies) { if (std::filesystem::exists(project_dir)) @@ -36,7 +38,7 @@ void GenerateProjectFiles(std::filesystem::path project_dir, const std::string& std::filesystem::create_directories(project_dir / "src"); std::filesystem::create_directories(project_dir / "include"); - std::string namespace_str = name; + std::string namespace_str = info.Name; namespace_str = replace_all(namespace_str, "-", "_"); namespace_str = replace_all(namespace_str, " ", "_"); @@ -55,7 +57,12 @@ void GenerateProjectFiles(std::filesystem::path project_dir, const std::string& return a.empty() ? "${" + b + "}" : a + " " + "${" + b + "}"; }); - const std::string find_lib_str = "find_library({{lib}} NAMES {{lib}} REQUIRED)"; + const std::string find_lib_str = "find_library({{lib}} NAMES {{lib}} PATH_SUFFIXES lib REQUIRED)\n"s + + "get_filename_component({{lib}}_PATH ${{{lib}}} DIRECTORY)\n"s + + "get_filename_component({{lib}}_PATH ${{{lib}}_PATH} DIRECTORY)\n"s + + "set({{lib}}_INCLUDE_DIR \"${{{lib}}_PATH}/include\")\n"s + + "list(APPEND FLOW_INCLUDE_DIR ${{{lib}}_INCLUDE_DIR})"s; + const std::string find_libs_str = std::accumulate(dependencies.begin(), dependencies.end(), std::string(""), [&](const std::string& a, const std::string& b) { return a + replace_all(find_lib_str, "{{lib}}", b) + "\n"; @@ -67,13 +74,12 @@ void GenerateProjectFiles(std::filesystem::path project_dir, const std::string& buffer << cmake_lists_template_fs.rdbuf(); std::string cmake_lists_template = buffer.str(); - cmake_lists_template = replace_all(cmake_lists_template, "{{name}}", name); - cmake_lists_template = replace_all(cmake_lists_template, "{{version}}", version); - cmake_lists_template = replace_all(cmake_lists_template, "{{find_libs}}", find_libs_str); - cmake_lists_template = replace_all(cmake_lists_template, "{{dependencies}}", dependencies_str); - cmake_lists_template = replace_all(cmake_lists_template, "{{export}}", export_str); - cmake_lists_template = replace_all(cmake_lists_template, "{{flow_dir}}", - replace_all(FileExplorer::GetExecutablePath().string(), "\\", "/")); + + cmake_lists_template = replace_all(cmake_lists_template, "{{name}}", info.Name); + cmake_lists_template = replace_all(cmake_lists_template, "{{version}}", info.Version); + cmake_lists_template = replace_all(cmake_lists_template, "{{find_libs}}", find_libs_str); + cmake_lists_template = replace_all(cmake_lists_template, "{{dependencies}}", dependencies_str); + cmake_lists_template = replace_all(cmake_lists_template, "{{export}}", export_str); std::ofstream cmake_lists_fs(project_dir / "CMakeLists.txt"); cmake_lists_fs << cmake_lists_template; @@ -93,9 +99,15 @@ void GenerateProjectFiles(std::filesystem::path project_dir, const std::string& std::ofstream register_source_fs(project_dir / "src" / "register.cpp"); register_source_fs << register_source_template; } + + // module.flowmod + { + std::ofstream module_file_fs(project_dir / (info.Name + ".flowmod")); + module_file_fs << json(info).dump(4); + } } -void BuildProject(std::filesystem::path project_dir) +bool BuildProject(std::filesystem::path project_dir) { const std::filesystem::path build_dir = project_dir / "build"; @@ -106,35 +118,56 @@ void BuildProject(std::filesystem::path project_dir) std::string build_cmd = "cmake --build {{build_dir}} -j"; build_cmd = replace_all(build_cmd, "{{build_dir}}", build_dir.string()); - std::system(configure_cmd.c_str()); - std::system(build_cmd.c_str()); -} - -NewModuleWindow::NewModuleWindow() : Window(Name) {} - -void NewModuleWindow::Draw() -try -{ - static auto name_input = std::make_shared>("name", _name); - static auto version_input = std::make_shared>("version", _version); - static auto author_input = std::make_shared>("author", _author); - static auto description_input = std::make_shared>("description", _description); - static auto dependencies = std::make_shared("dependencies", 2); - - static auto flow_core_dep = std::make_shared>("flow-core", true); - if (flow_core_dep.use_count() == 1) + if (std::system(configure_cmd.c_str())) { - dependencies->AddEntry(flow_core_dep); - dependencies->AddEntry(std::make_shared("flow-core")); + return false; } - static auto flow_ui_dep = std::make_shared>("flow-ui", false); - if (flow_ui_dep.use_count() == 1) + if (std::system(build_cmd.c_str())) { - dependencies->AddEntry(flow_ui_dep); - dependencies->AddEntry(std::make_shared("flow-ui")); + return false; } + return true; +} + +NewModuleWindow::NewModuleWindow() : Window(Name) +{ + name_input = std::make_shared>("name", ""); + version_input = std::make_shared>("version", ""); + author_input = std::make_shared>("author", ""); + description_input = std::make_shared>("description", ""); + dependencies = std::make_shared("dependencies", 2); + + auto flow_core_dep = std::make_shared>("flow-core", true); + dependencies->AddEntry(flow_core_dep); + dependencies->AddEntry(std::make_shared("flow-core")); + + auto flow_ui_dep = std::make_shared>("flow-ui", false); + dependencies->AddEntry(flow_ui_dep); + dependencies->AddEntry(std::make_shared("flow-ui")); +} + +void NewModuleWindow::Clear() +{ + name_input.reset(new widgets::Input("name", "")); + version_input.reset(new widgets::Input("version", "")); + author_input.reset(new widgets::Input("author", "")); + description_input.reset(new widgets::Input("description", "")); + dependencies.reset(new widgets::Table("dependencies", 2)); + + auto flow_core_dep = std::make_shared>("flow-core", true); + dependencies->AddEntry(flow_core_dep); + dependencies->AddEntry(std::make_shared("flow-core")); + + auto flow_ui_dep = std::make_shared>("flow-ui", false); + dependencies->AddEntry(flow_ui_dep); + dependencies->AddEntry(std::make_shared("flow-ui")); +} + +bool NewModuleWindow::DrawAndCreate() +try +{ widgets::Table new_module_form("new_module_form", 2); new_module_form.AddEntry(std::make_shared("Name")); new_module_form.AddEntry(name_input); @@ -149,30 +182,40 @@ try new_module_form(); - if (auto _ = name_input->GetData()) - { - _name = name_input->GetValue(); - } - + bool created = false; ImGui::BeginHorizontal("buttons"); ImGui::Spring(); if (ImGui::Button("Create")) { - const auto& project_dir = FileExplorer::GetDocumentsPath() / _name; + ImGui::CloseCurrentPopup(); - GenerateProjectFiles(project_dir, _name, _version, {"flow-core", "flow-ui"}); - BuildProject(project_dir); + const auto& name = name_input->GetValue(); + const auto& project_dir = FileExplorer::GetDocumentsPath() / name; - try + const auto& flow_core_dep = std::static_pointer_cast>(dependencies->GetEntry(0)); + const auto& flow_ui_dep = std::static_pointer_cast>(dependencies->GetEntry(2)); + std::vector chosen_deps; + + if (flow_core_dep->GetValue()) { - // std::filesystem::copy_file(project_dir / "build" / "Debug" / "cmath.dll", _modules_path / "cmath.dll", - // std::filesystem::copy_options::overwrite_existing); + chosen_deps.push_back("flow-core"); } - catch (...) + + if (flow_ui_dep->GetValue()) { + chosen_deps.push_back("flow-ui"); } - ImGui::CloseCurrentPopup(); + GenerateProjectFiles(project_dir, + ModuleInfo{ + .Name = name, + .Version = version_input->GetValue(), + .Author = author_input->GetValue(), + .Description = description_input->GetValue(), + }, + chosen_deps); + + created = BuildProject(project_dir); } ImGui::SetItemDefaultFocus(); @@ -181,10 +224,13 @@ try ImGui::CloseCurrentPopup(); } ImGui::EndHorizontal(); + + return created; } catch (...) { Window::Draw(); + return false; } FLOW_UI_NAMESPACE_END diff --git a/templates/ModuleCMakeLists.txt.in b/templates/ModuleCMakeLists.txt.in index 2666c30..695e6d3 100644 --- a/templates/ModuleCMakeLists.txt.in +++ b/templates/ModuleCMakeLists.txt.in @@ -20,4 +20,4 @@ if(MSVC) endif() target_link_libraries(${PROJECT_NAME} PUBLIC {{dependencies}}) -target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include "{{flow_dir}}/include") +target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include ${FLOW_INCLUDE_DIR}) From c8b9de70b71495d93d9d712fa34a026a1ad2ef01 Mon Sep 17 00:00:00 2001 From: GhostofCookie Date: Thu, 24 Apr 2025 11:18:56 -0400 Subject: [PATCH 3/5] Fix apple build. --- src/windows/ModuleManagerWindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/windows/ModuleManagerWindow.cpp b/src/windows/ModuleManagerWindow.cpp index 3cb9ad2..a7eddb4 100644 --- a/src/windows/ModuleManagerWindow.cpp +++ b/src/windows/ModuleManagerWindow.cpp @@ -18,7 +18,7 @@ FLOW_UI_NAMESPACE_START const std::string module_file_extension = "flowmod"; #ifdef FLOW_WINDOWS const std::string module_binary_extension = ".dll"; -#elif FLOW_APPLE +#elif defined(FLOW_APPLE) const std::string module_binary_extension = ".dylib"; #else const std::string module_binary_extension = ".so"; From 969f0c05d7c330f052c7002bb5b45e5aec7e8b1a Mon Sep 17 00:00:00 2001 From: GhostofCookie Date: Wed, 11 Jun 2025 12:34:54 -0400 Subject: [PATCH 4/5] Update and improve. --- CMakeLists.txt | 4 +- examples/simple_module/include/Example.hpp | 1 + include/flow/ui/views/NodeView.hpp | 10 +- include/flow/ui/views/PortView.hpp | 11 +- include/flow/ui/windows/GraphWindow.hpp | 6 +- src/Editor.cpp | 40 +++---- src/EditorNodes.hpp | 124 ++------------------ src/views/NodeView.cpp | 109 ++++++++--------- src/views/PortView.cpp | 10 +- src/windows/GraphWindow.cpp | 107 ++++++++++------- src/windows/ModuleManagerWindow.cpp | 130 ++++----------------- 11 files changed, 188 insertions(+), 364 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index aa05d15..ff1e2a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,7 +39,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake") include(cmake/CPM.cmake) -CPMAddPackage("gh:InFlowStructure/flow-core@1.2.0") +CPMAddPackage("gh:InFlowStructure/flow-core#32a2e54") CPMAddPackage("gh:btzy/nativefiledialog-extended#29e3bcb") FetchContent_Declare( @@ -139,7 +139,7 @@ add_library(flow-ui::flow-ui ALIAS flow-ui) if(MSVC) add_compile_definitions(_CRT_SECURE_NO_WARNINGS) - target_compile_options(flow-ui PRIVATE /W4) + target_compile_options(flow-ui PRIVATE /W4 /MP) endif() target_include_directories(flow-ui diff --git a/examples/simple_module/include/Example.hpp b/examples/simple_module/include/Example.hpp index 4a31cfd..fb47521 100644 --- a/examples/simple_module/include/Example.hpp +++ b/examples/simple_module/include/Example.hpp @@ -5,6 +5,7 @@ #include #include +#include #include diff --git a/include/flow/ui/views/NodeView.hpp b/include/flow/ui/views/NodeView.hpp index ed18b91..3e94716 100644 --- a/include/flow/ui/views/NodeView.hpp +++ b/include/flow/ui/views/NodeView.hpp @@ -70,7 +70,7 @@ class NodeView : public GraphItemView * @param node The node to represent. * @param header_colour The colour of the node header. */ - NodeView(flow::SharedNode node, Colour header_colour = Colour(40, 75, 99)); + NodeView(const flow::SharedNode& node, Colour header_colour = Colour(40, 75, 99)); virtual ~NodeView() = default; @@ -86,6 +86,9 @@ class NodeView : public GraphItemView void ShowConnectables(const std::shared_ptr& port) override; public: + /// The ID of the node this view is for. + UUID NodeID; + /// The name of the Node. std::string Name; @@ -98,9 +101,6 @@ class NodeView : public GraphItemView /// The colour of the header. Colour HeaderColour; - /// The node that is being represented. - flow::SharedNode Node; - protected: std::shared_ptr _builder; bool _received_error = false; @@ -116,7 +116,7 @@ class SimpleNodeView : public NodeView * @brief Constructs a simple node view. * @param node The node being represented. */ - SimpleNodeView(flow::SharedNode node); + SimpleNodeView(const flow::SharedNode& node); virtual ~SimpleNodeView() = default; diff --git a/include/flow/ui/views/PortView.hpp b/include/flow/ui/views/PortView.hpp index 4275a63..47ada5e 100644 --- a/include/flow/ui/views/PortView.hpp +++ b/include/flow/ui/views/PortView.hpp @@ -94,12 +94,6 @@ class PortView */ const flow::IndexableName& Key() const noexcept { return _port->GetKey(); } - /** - * @brief Gets the name/label of the port. - * @returns The port's name. - */ - std::string_view Name() const noexcept { return _port->GetVarName(); } - /** * @brief Gets the caption/description of the port. * @returns The port's caption. @@ -148,7 +142,7 @@ class PortView std::uint64_t ID; /// The owning NodeView ID. - const std::uint64_t& NodeID; + const std::uint64_t& NodeViewID; /// The type of port. PortType Kind = PortType::Input; @@ -156,6 +150,9 @@ class PortView /// Event run on setting a new value in the input field. InputEvent OnSetInput; + /// The name of the port. + std::string Name; + private: std::shared_ptr _port; std::shared_ptr _input_field; diff --git a/include/flow/ui/windows/GraphWindow.hpp b/include/flow/ui/windows/GraphWindow.hpp index f24d0f4..ef98fda 100644 --- a/include/flow/ui/windows/GraphWindow.hpp +++ b/include/flow/ui/windows/GraphWindow.hpp @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -209,6 +210,7 @@ class GraphWindow : public Window flow::SharedNode CreateNode(const std::string& class_name, const std::string& display_name); private: + mutable std::mutex _mutex; std::unique_ptr _editor_ctx; std::shared_ptr _graph; @@ -221,9 +223,7 @@ class GraphWindow : public Window std::shared_ptr _new_node_link_pin = nullptr; std::shared_ptr _new_link_pin = nullptr; - std::string node_lookup; - - ContextMenu node_creation_context_menu; + ContextMenu _node_creation_context_menu; struct { diff --git a/src/Editor.cpp b/src/Editor.cpp index 79b61a4..f7cb5f0 100644 --- a/src/Editor.cpp +++ b/src/Editor.cpp @@ -13,6 +13,7 @@ #include "windows/PropertyWindow.hpp" #include "windows/ShortcutsWindow.hpp" +#include #include #include #include @@ -22,6 +23,7 @@ #include #include #include +#include #include #include @@ -100,6 +102,14 @@ Editor::Editor(const std::string& initial_file) }; } +int return_test_method(const int& i, int& j) { return i * (j += 1); } +void void_test_method(int i, int& j) { j = i * 1000; } +void rvalue_test_method(int& i, int&& j) +{ + i = std::move(j); + return; +} + void Editor::Init(const std::string& initial_file) { _factory->OnNodeClassUnregistered.Bind("Unregister", [&](std::string_view class_name) { @@ -125,6 +135,11 @@ void Editor::Init(const std::string& initial_file) }); _factory->RegisterNodeClass("Editor", "Preview"); + + _factory->RegisterFunction("TEST", "test_method"); + _factory->RegisterFunction("TEST", "void_test_method"); + _factory->RegisterFunction("TEST", "rvalue_test_method"); + _factory->RegisterNodeView(); _factory->RegisterInputType(false); @@ -281,31 +296,6 @@ void Editor::DrawMainMenuBar() SaveFlow(); } - if (ImGui::MenuItem("Save As")) - { - SaveFlow(); - } - - if (ImGui::MenuItem("Import Module")) - { - const auto filename = FileExplorer::Load(default_modules_path, "Flow Modules", "so,dll,dylib"); - const auto new_module_file = default_modules_path / filename.filename(); - - if (new_module_file != filename) - { - try - { - std::filesystem::create_directory(default_modules_path); - std::filesystem::copy_file(filename, new_module_file, std::filesystem::copy_options::skip_existing); - _env->LoadModule(new_module_file); - } - catch (const std::exception& e) - { - SPDLOG_ERROR("Caught exception while loading module {}: {}", filename.string(), e.what()); - } - } - } - ImGui::EndMenu(); } diff --git a/src/EditorNodes.hpp b/src/EditorNodes.hpp index 2df8bab..ebc5a9c 100644 --- a/src/EditorNodes.hpp +++ b/src/EditorNodes.hpp @@ -6,17 +6,21 @@ #include "utilities/Builders.hpp" #include "utilities/Conversions.hpp" #include "views/NodeView.hpp" +#include "widgets/Text.hpp" #include #include #include #include +#include + +#include FLOW_UI_NAMESPACE_START struct PreviewNodeView : NodeView { - using NodeView::NodeView; + PreviewNodeView(flow::SharedNode node) : NodeView(node), Node(node) {} virtual ~PreviewNodeView() = default; @@ -27,16 +31,15 @@ struct PreviewNodeView : NodeView _builder->Header(utility::to_ImColor(HeaderColour)); ImGui::Spring(0); - const auto& name = Node->GetName().c_str(); if (GetConfig().NodeHeaderFont) { ImGui::PushFont(std::bit_cast(GetConfig().NodeHeaderFont.get())); - ImGui::TextUnformatted(name); + ImGui::TextUnformatted(Name.c_str()); ImGui::PopFont(); } else { - ImGui::TextUnformatted(name); + ImGui::TextUnformatted(Name.c_str()); } ImGui::Spring(1); @@ -57,7 +60,7 @@ struct PreviewNodeView : NodeView _builder->EndHeader(); - auto input_it = std::find_if(Inputs.begin(), Inputs.end(), [](const auto& in) { return in->Name() == "in"; }); + auto input_it = std::find_if(Inputs.begin(), Inputs.end(), [](const auto& in) { return in->Name == "in"; }); const auto& input = *input_it; input->SetShowLabel(false); @@ -96,119 +99,10 @@ struct PreviewNodeView : NodeView _builder->End(); } -}; - -template -struct FunctionTraits; -template -struct FunctionTraits -{ - using ReturnType = std::invoke_result_t; - using ArgTypes = std::tuple; + SharedNode Node; }; -template Func> -class FunctionWrapperNode : public Node -{ - protected: - using FuncTraits = FunctionTraits; - using OutputType = typename FuncTraits::ReturnType; - - private: - template - void AddInputs(std::integer_sequence) - { - (AddInput>( - {input_names[Idx] = "in" + std::to_string(Idx)}, ""), - ...); - } - - template - auto GetInputs(std::integer_sequence) - { - return std::make_tuple( - GetInputData>(IndexableName{input_names[Idx]})...); - } - - template - json SaveInputs(std::integer_sequence) const - { - json inputs_json; - ( - [&, this] { - const auto& key = input_names[Idx]; - if (auto x = GetInputData>(IndexableName{key})) - { - inputs_json[key] = x->Get(); - } - }(), - ...); - - return inputs_json; - } - - template - void RestoreInputs(json& j, std::integer_sequence) - { - ( - [&, this] { - const auto& key = input_names[Idx]; - if (!j.contains(key)) - { - return; - } - - SetInputData(IndexableName{key}, - MakeNodeData>(j[key]), false); - }(), - ...); - } - - public: - explicit FunctionWrapperNode(const std::string& uuid_str, const std::string& name, std::shared_ptr env) - : Node(uuid_str, TypeName_v>, name, std::move(env)), _func{Func} - { - AddInputs(std::make_integer_sequence>{}); - - AddOutput("result", "result"); - } - - virtual ~FunctionWrapperNode() = default; - - protected: - void Compute() override - { - auto inputs = GetInputs(std::make_integer_sequence>{}); - - if (std::apply([](auto&&... args) { return (!args || ...); }, inputs)) return; - - auto result = std::apply([&](auto&&... args) { return _func(args->Get()...); }, inputs); - this->SetOutputData("result", MakeNodeData(std::move(result))); - } - - json SaveInputs() const override - { - return SaveInputs(std::make_integer_sequence>{}); - } - - void RestoreInputs(const json& j) override - { - RestoreInputs(const_cast(j), - std::make_integer_sequence>{}); - } - - private: - std::add_pointer_t _func; - std::array> input_names{""}; -}; - -#ifndef FLOW_WINDOWS -#define WRAP_FUNCTION_IN_NODE_TYPE(func, ...) FunctionWrapperNode -#else -#define WRAP_FUNCTION_IN_NODE_TYPE(func, ...) FunctionWrapperNode -#endif - FLOW_UI_NAMESPACE_END struct PreviewNode : public flow::Node diff --git a/src/views/NodeView.cpp b/src/views/NodeView.cpp index 3e6412c..7888454 100644 --- a/src/views/NodeView.cpp +++ b/src/views/NodeView.cpp @@ -55,27 +55,27 @@ GraphItemView::~GraphItemView() void GraphItemView::ShowConnectables(const std::shared_ptr&) {} -NodeView::NodeView(flow::SharedNode node, Colour header_colour) -try : GraphItemView(std::hash{}(node->ID())), Name(node->GetName()), - HeaderColour(header_colour), Node{std::move(node)}, _builder{std::make_shared()} +NodeView::NodeView(const flow::SharedNode& node, Colour header_colour) +try : GraphItemView(std::hash{}(node->ID())), NodeID(node->ID()), Name(node->GetName()), + HeaderColour(header_colour), _builder{std::make_shared()} { - Node->OnCompute.Bind("ClearError", [&]() { _received_error = false; }); - Node->OnError.Bind("SetError", [&](const std::exception&) { _received_error = true; }); + node->OnCompute.Bind("ClearError", [&]() { _received_error = false; }); + node->OnError.Bind("SetError", [&](const std::exception&) { _received_error = true; }); - auto on_input = [this](const auto& key, auto data) { - Node->GetEnv()->AddTask([key, c = Node, d = std::move(data)] { + auto on_input = [this, env = node->GetEnv(), n = node](const auto& key, auto data) { + env->AddTask([key, c = std::move(n), d = std::move(data)] { std::lock_guard _(*c); c->SetInputData(key, std::move(d)); }); }; std::vector sorted_ports; - auto ins = Node->GetInputPorts(); + auto ins = node->GetInputPorts(); std::for_each(ins.begin(), ins.end(), [&](const auto p) { sorted_ports.emplace_back(p.second); }); std::sort(sorted_ports.begin(), sorted_ports.end(), std::less()); - auto view_factory = std::dynamic_pointer_cast(Node->GetEnv()->GetFactory()); + auto view_factory = std::dynamic_pointer_cast(node->GetEnv()->GetFactory()); Inputs.reserve(sorted_ports.size()); for (const auto& input : sorted_ports) @@ -85,8 +85,8 @@ try : GraphItemView(std::hash{}(node->ID())), Name(node->GetName()), in->SetBuilder(_builder); } - Outputs.reserve(Node->GetOutputPorts().size()); - for (const auto& [_, output] : Node->GetOutputPorts()) + Outputs.reserve(node->GetOutputPorts().size()); + for (const auto& [_, output] : node->GetOutputPorts()) { auto& out = Outputs.emplace_back(std::make_shared(_id, output, view_factory, on_input)); out->Kind = PortType::Output; @@ -101,7 +101,7 @@ catch (const std::exception& e) void NodeView::Draw() try { - const auto& name = Node->GetName().c_str(); + const auto& name = Name.c_str(); if (_received_error) { @@ -146,7 +146,7 @@ try } catch (const std::exception& e) { - SPDLOG_ERROR("Caught exception while trying to draw node '{0}': {1}", std::string(Node->ID()), e.what()); + SPDLOG_ERROR("Caught exception while trying to draw node '{0}': {1}", std::string(NodeID), e.what()); } _builder->End(); @@ -174,7 +174,7 @@ void NodeView::ShowConnectables(const std::shared_ptr& new_link_pin) } } -SimpleNodeView::SimpleNodeView(flow::SharedNode node) : NodeView(std::move(node)) +SimpleNodeView::SimpleNodeView(const flow::SharedNode& node) : NodeView(node) { for (const auto& input : Inputs) { @@ -190,8 +190,6 @@ SimpleNodeView::SimpleNodeView(flow::SharedNode node) : NodeView(std::move(node) void SimpleNodeView::Draw() try { - const auto& name = Node->GetName().c_str(); - if (_received_error) { ed::PushStyleColor(ed::StyleColor_NodeBorder, ImColor(227, 36, 27)); @@ -210,7 +208,7 @@ try _builder->Middle(); - const ImVec2 text_size = ImGui::CalcTextSize(name) * 2.f; + const ImVec2 text_size = ImGui::CalcTextSize(Name.c_str()) * 2.f; ImGui::Dummy(text_size); for (auto& output : Outputs) @@ -220,7 +218,7 @@ try } catch (const std::exception& e) { - SPDLOG_ERROR("Caught exception while trying to draw node '{0}': {1}", std::string(Node->ID()), e.what()); + SPDLOG_ERROR("Caught exception while trying to draw node '{0}': {1}", std::string(NodeID), e.what()); } ImGui::PopStyleColor(); @@ -228,10 +226,10 @@ try _builder->End(); auto draw_list = ed::GetNodeBackgroundDrawList(_id); - const ImVec2 text_size = ImGui::CalcTextSize(name) * 2.f; + const ImVec2 text_size = ImGui::CalcTextSize(Name.c_str()) * 2.f; draw_list->AddText(std::bit_cast(GetConfig().NodeHeaderFont.get()), 40.f, _builder->GetMinPos() - (text_size / 2) + (_builder->GetSize() / 2), - IM_COL32(180, 180, 180, 255), name); + IM_COL32(180, 180, 180, 255), Name.c_str()); if (_received_error) { @@ -250,9 +248,7 @@ CommentView::CommentView(CommentSize size, std::string_view name) void CommentView::Draw() { - const float commentAlpha = 0.75f; - - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, commentAlpha); + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.75f); ed::PushStyleColor(ed::StyleColor_NodeBg, ImColor(0, 0, 0, 64)); ed::PushStyleColor(ed::StyleColor_NodeBorder, ImColor(0, 0, 0, 64)); ed::PushStyleVar(ed::StyleVar_NodeRounding, 0.f); @@ -262,53 +258,48 @@ void CommentView::Draw() ed::BeginNode(ID()); - ImVec2 HeaderMin(0.f, 0.f); - ImVec2 HeaderMax(0.f, 0.f); - ImGui::PushID(std::bit_cast(ID())); auto cursor_pos_x = ImGui::GetCursorPosX(); ImGui::BeginVertical("content"); - { - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.f, 0.f)); - ImGui::BeginHorizontal("horizontal"); - { - ImGui::PushFont(std::bit_cast(GetConfig().NodeHeaderFont.get())); - ImGui::Dummy(ImVec2(5.f, 0)); - - if (_edit) - { - ImGui::PushItemWidth(std::min(Size.Width, ImGui::CalcTextSize(Name.c_str()).x)); - if (ImGui::InputText("", &Name, - ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EnterReturnsTrue)) - { - _edit = false; - } - ImGui::PopItemWidth(); - } - else - { - ImGui::PushTextWrapPos(cursor_pos_x + Size.Width * 0.99f); - ImGui::TextWrapped("%s", Name.c_str()); - ImGui::PopTextWrapPos(); - } - - ImGui::PopFont(); - } - ImGui::EndHorizontal(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.f, 0.f)); + ImGui::BeginHorizontal("horizontal"); + + ImGui::PushFont(std::bit_cast(GetConfig().NodeHeaderFont.get())); + ImGui::Dummy(ImVec2(5.f, 0)); - if (ImGui::IsItemClicked()) + if (_edit) + { + ImGui::PushItemWidth(std::min(Size.Width, ImGui::CalcTextSize(Name.c_str()).x)); + if (ImGui::InputText("", &Name, ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EnterReturnsTrue)) { - _edit = true; + _edit = false; } + ImGui::PopItemWidth(); + } + else + { + ImGui::PushTextWrapPos(cursor_pos_x + Size.Width * 0.99f); + ImGui::TextWrapped("%s", Name.c_str()); + ImGui::PopTextWrapPos(); + } - ImGui::PopStyleVar(); + ImGui::PopFont(); - HeaderMin = ImGui::GetItemRectMin(); - HeaderMax = ImGui::GetItemRectMax(); + ImGui::EndHorizontal(); - ed::Group(ImVec2{Size.Width, Size.Height}); + if (ImGui::IsItemClicked()) + { + _edit = true; } + + ImGui::PopStyleVar(); + + ImVec2 HeaderMin = ImGui::GetItemRectMin(); + ImVec2 HeaderMax = ImGui::GetItemRectMax(); + + ed::Group(ImVec2{Size.Width, Size.Height}); + ImGui::EndVertical(); Size.Width = (ImGui::GetItemRectMax() - ImGui::GetItemRectMin()).x; ImGui::PopID(); diff --git a/src/views/PortView.cpp b/src/views/PortView.cpp index ad12ce2..614b42d 100644 --- a/src/views/PortView.cpp +++ b/src/views/PortView.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -49,13 +50,13 @@ void DrawPinIcon(const PortView& pin, bool connected, int alpha) PortView::PortView(const std::uint64_t& node_id, std::shared_ptr port_data, const std::shared_ptr& factory, InputEvent input_function, bool show_label) - : ID(std::hash{}({})), NodeID(node_id), _port{std::move(port_data)}, + : ID(std::hash{}({})), NodeViewID(node_id), Name(port_data->GetVarName()), _port{std::move(port_data)}, _show_label{_port->GetKey() != flow::IndexableName::None && show_label}, OnSetInput{input_function} { const auto& input_ctors = factory->GetRegisteredInputTypes(); if (input_ctors.contains(std::string{Type()})) { - _input_field = input_ctors.at(std::string{Type()})(std::string{Name()}, _port->GetData()); + _input_field = input_ctors.at(std::string{Type()})(Name, _port->GetData()); } } @@ -94,6 +95,7 @@ void PortView::Draw() { _builder->Output(ID); ImGui::PushStyleVar(ImGuiStyleVar_Alpha, _alpha); + ImGui::Spring(1); DrawLabel(); DrawIcon(_alpha); ImGui::PopStyleVar(); @@ -106,7 +108,7 @@ constexpr std::string_view AnyType = flow::TypeName_v; bool PortView::CanLink(const std::shared_ptr& other) const noexcept { if (IsConnected() && Kind == PortType::Input || !other || ID == other->ID || Kind == other->Kind || - NodeID == other->NodeID) + NodeViewID == other->NodeViewID) { return false; } @@ -136,7 +138,7 @@ void PortView::DrawLabel() if (!_show_label) return; ImGui::AlignTextToFramePadding(); - ImGui::TextUnformatted(std::string{Name().substr(0, Name().find("##"))}.c_str()); + ImGui::TextUnformatted(std::string{Name.substr(0, Name.find("##"))}.c_str()); } void PortView::DrawIcon(float alpha) { ::flow::ui::DrawPinIcon(*this, IsConnected(), static_cast(alpha * 255)); } diff --git a/src/windows/GraphWindow.cpp b/src/windows/GraphWindow.cpp index a3edf50..408a9d5 100644 --- a/src/windows/GraphWindow.cpp +++ b/src/windows/GraphWindow.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -205,7 +206,7 @@ constexpr GraphWindow::ActionType operator&(const GraphWindow::ActionType& a, co } GraphWindow::GraphWindow(std::shared_ptr graph) - : Window(graph->GetName()), _graph{std::move(graph)}, node_creation_context_menu{GetEnv()->GetFactory()} + : Window(graph->GetName()), _graph{std::move(graph)}, _node_creation_context_menu{GetEnv()->GetFactory()} { ed::Config config; config.UserPointer = this; @@ -249,9 +250,7 @@ GraphWindow::GraphWindow(std::shared_ptr graph) ed_colours[utility::to_EdStyleColour(i)] = utility::to_ImColor(c); }); - _graph->Visit([](const auto& node) { return node->Start(); }); - - node_creation_context_menu.OnSelection = + _node_creation_context_menu.OnSelection = [this, factory = std::dynamic_pointer_cast(GetEnv()->GetFactory())](const auto& class_name, const auto& display_name) { CreateNode(class_name, display_name); @@ -278,10 +277,10 @@ GraphWindow::GraphWindow(std::shared_ptr graph) auto end_pin = pin; if (start_pin->Kind == PortType::Input) std::swap(start_pin, end_pin); - const auto& start_node = FindNode(start_pin->NodeID)->Node; - const auto& end_node = FindNode(end_pin->NodeID)->Node; - const auto& conn = - _graph->ConnectNodes(start_node->ID(), start_pin->Name(), end_node->ID(), end_pin->Name()); + const auto& start_node = FindNode(start_pin->NodeViewID); + const auto& end_node = FindNode(end_pin->NodeViewID); + const auto& conn = _graph->ConnectNodes(start_node->NodeID, IndexableName{start_pin->Name}, + end_node->NodeID, IndexableName{end_pin->Name}); _links.emplace(std::hash{}(conn->ID()), ConnectionView{conn->ID(), start_pin->ID, end_pin->ID, start_pin->GetColour()}); @@ -289,6 +288,8 @@ GraphWindow::GraphWindow(std::shared_ptr graph) } } }); + + _graph->Visit([](const auto& node) { return node->Start(); }); } GraphWindow::~GraphWindow() @@ -319,7 +320,10 @@ void GraphWindow::Draw() try { ImGuiWindowFlags window_flags = ImGuiWindowFlags_None; - if (_dirty) window_flags |= ImGuiWindowFlags_UnsavedDocument; + if (_dirty) + { + window_flags |= ImGuiWindowFlags_UnsavedDocument; + } ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); @@ -355,21 +359,25 @@ try CleanupDeadItems(); - for (auto& [_, item] : _item_views) { - item->ShowConnectables(_new_link_pin); - item->Draw(); - } + std::lock_guard _(_mutex); + for (auto& [__, item] : _item_views) + { + item->ShowConnectables(_new_link_pin); + item->Draw(); + } - for (auto& [_, link] : _links) - { - link.Draw(); + for (auto& [__, link] : _links) + { + link.Draw(); + } } ImGui::SetCursorScreenPos(cursorTopLeft); ed::Suspend(); + // TODO: Do something more official here. static ed::NodeId context_node_id = 0; static ed::PinId context_pin_id = 0; @@ -465,7 +473,7 @@ try ImGui::EndPopup(); } - node_creation_context_menu(); + _node_creation_context_menu(); ImGui::PopStyleColor(); ImGui::PopStyleVar(); @@ -538,13 +546,17 @@ void GraphWindow::EndDraw() { ed::End(); } + ImGui::End(); ImGui::PopStyleVar(); } std::shared_ptr GraphWindow::FindNode(std::uint64_t id) const { - if (!id) throw std::invalid_argument("Node ID cannot be null"); + if (!id) + { + throw std::invalid_argument("Node ID cannot be null"); + } if (_item_views.contains(id)) { @@ -556,31 +568,44 @@ std::shared_ptr GraphWindow::FindNode(std::uint64_t id) const ConnectionView& GraphWindow::FindConnection(std::uint64_t id) { - if (!id) throw std::invalid_argument("Link ID cannot be null"); + if (!id) + { + throw std::invalid_argument("Link ID cannot be null"); + } return _links.at(id); } std::shared_ptr GraphWindow::FindPort(std::uint64_t id) const { - if (!id) throw std::invalid_argument("Pin ID cannot be null"); + if (!id) + { + throw std::invalid_argument("Pin ID cannot be null"); + } for (auto& [_, item] : _item_views) { auto node = std::dynamic_pointer_cast(item); - if (!node) continue; + if (!node) + { + continue; + } for (auto& pin : node->Inputs) + { if (pin->ID == id) { return pin; } + } for (auto& pin : node->Outputs) + { if (pin->ID == id) { return pin; } + } } return nullptr; @@ -588,7 +613,10 @@ std::shared_ptr GraphWindow::FindPort(std::uint64_t id) const std::shared_ptr GraphWindow::FindComment(std::uint64_t id) const { - if (!id) throw std::invalid_argument("Comment ID cannot be null"); + if (!id) + { + throw std::invalid_argument("Comment ID cannot be null"); + } if (_item_views.contains(id)) { @@ -626,7 +654,7 @@ void GraphWindow::DeleteNode(std::uint64_t id) DeleteLink(link_id); } - _graph->RemoveNode(node->Node); + _graph->RemoveNodeByID(node->NodeID); } _item_views.erase(id); @@ -644,10 +672,10 @@ bool GraphWindow::DeleteLink(std::uint64_t id) auto start_pin = FindPort(link.StartPortID); auto end_pin = FindPort(link.EndPortID); - auto start_node = FindNode(start_pin->NodeID); - auto end_node = FindNode(end_pin->NodeID); + auto start_node = FindNode(start_pin->NodeViewID); + auto end_node = FindNode(end_pin->NodeViewID); - _graph->DisconnectNodes(start_node->Node->ID(), start_pin->Key(), end_node->Node->ID(), end_pin->Key()); + _graph->DisconnectNodes(start_node->NodeID, start_pin->Key(), end_node->NodeID, end_pin->Key()); return _links.erase(id) != 0; } @@ -685,7 +713,7 @@ void GraphWindow::CreateItems() std::swap(start_pin_id, end_pin_id); } - if (&end_pin == &start_pin || &start_pin->NodeID == &end_pin->NodeID || end_pin->IsConnected()) + if (&end_pin == &start_pin || &start_pin->NodeViewID == &end_pin->NodeViewID || end_pin->IsConnected()) { ed::RejectNewItem(ImColor(255, 0, 0), 2.0f); } @@ -711,10 +739,10 @@ void GraphWindow::CreateItems() DrawLabel(label.c_str(), ImColor(32, 45, 32, 180)); if (ed::AcceptNewItem(ImColor(128, 255, 128), 4.0f)) { - const auto& start_node = FindNode(start_pin->NodeID)->Node; - const auto& end_node = FindNode(end_pin->NodeID)->Node; - const auto& conn = - _graph->ConnectNodes(start_node->ID(), start_pin->Name(), end_node->ID(), end_pin->Name()); + const auto& start_node = FindNode(start_pin->NodeViewID); + const auto& end_node = FindNode(end_pin->NodeViewID); + const auto& conn = _graph->ConnectNodes(start_node->NodeID, IndexableName{start_pin->Name}, + end_node->NodeID, IndexableName{end_pin->Name}); _links.emplace(std::hash{}(conn->ID()), ConnectionView{conn->ID(), start_pin->ID, end_pin->ID, start_pin->GetColour()}); @@ -776,9 +804,9 @@ void GraphWindow::OnLoadNode(const flow::SharedNode& node, const json& position_ { auto view_factory = std::dynamic_pointer_cast(GetEnv()->GetFactory()); node_view = view_factory->CreateNodeView(node); - node_view->Node->OnSetOutput.Bind( - "ShowLinkFlowing", - [=, this, node_id = node->ID()](const IndexableName& key, auto) { ShowLinkFlowing(node_id, key); }); + node->OnSetOutput.Bind("ShowLinkFlowing", [=, this, node_id = node->ID()](const IndexableName& key, auto) { + ShowLinkFlowing(node_id, key); + }); _item_views.emplace(node_view->ID(), node_view); } @@ -796,9 +824,9 @@ void GraphWindow::OnLoadConnection(const flow::SharedConnection& connection) auto end_node = FindNode(end_node_id); auto start_pin = std::find_if(start_node->Outputs.begin(), start_node->Outputs.end(), - [&](auto&& pin) { return pin->Name() == connection->StartPortKey(); }); + [&](auto&& pin) { return IndexableName{pin->Name} == connection->StartPortKey(); }); auto end_pin = std::find_if(end_node->Inputs.begin(), end_node->Inputs.end(), - [&](auto&& pin) { return pin->Name() == connection->EndPortKey(); }); + [&](auto&& pin) { return IndexableName{pin->Name} == connection->EndPortKey(); }); _links.emplace(std::hash{}(connection->ID()), ConnectionView{connection->ID(), (*start_pin)->ID, (*end_pin)->ID, (*start_pin)->GetColour()}); @@ -991,10 +1019,11 @@ void GraphWindow::CreateNodesAction(const json& SPDLOG_json) auto start_node = FindNode(start_node_id); auto end_node = FindNode(end_node_id); - auto start_pin = std::find_if(start_node->Outputs.begin(), start_node->Outputs.end(), - [&](auto&& pin) { return pin->Name() == connection->StartPortKey(); }); + auto start_pin = std::find_if(start_node->Outputs.begin(), start_node->Outputs.end(), [&](auto&& pin) { + return IndexableName{pin->Name} == connection->StartPortKey(); + }); auto end_pin = std::find_if(end_node->Inputs.begin(), end_node->Inputs.end(), - [&](auto&& pin) { return pin->Name() == connection->EndPortKey(); }); + [&](auto&& pin) { return IndexableName{pin->Name} == connection->EndPortKey(); }); _links.emplace(std::hash{}(connection->ID()), ConnectionView{connection->ID(), (*start_pin)->ID, (*end_pin)->ID, (*start_pin)->GetColour()}); diff --git a/src/windows/ModuleManagerWindow.cpp b/src/windows/ModuleManagerWindow.cpp index a7eddb4..1905cdf 100644 --- a/src/windows/ModuleManagerWindow.cpp +++ b/src/windows/ModuleManagerWindow.cpp @@ -2,11 +2,12 @@ #include "FileExplorer.hpp" #include "InputField.hpp" -#include "ModuleInfo.hpp" #include "Text.hpp" #include "Widget.hpp" #include "utilities/Conversions.hpp" +#include +#include #include #include @@ -27,61 +28,17 @@ const std::string module_binary_extension = ".so"; class ModuleView : public Widget { public: - ModuleView(const std::filesystem::path& name, std::shared_ptr env, const ModuleInfo& info) - : _binary_path(name), _env(std::move(env)), _info(info), - _enabled(name.filename().replace_extension("").string(), true) + ModuleView(const std::filesystem::path& name, std::shared_ptr env) + : _binary_path(name), _enabled(name.filename().replace_extension("").string(), true) { - if (_enabled.GetValue()) - { - _env->LoadModule(_binary_path); - } - else - { - _env->UnloadModule(_binary_path); - } - } - - ModuleView(const std::filesystem::path& dir, std::shared_ptr env) - : _env(std::move(env)), _enabled(dir.filename().replace_extension("").string(), true) - { - std::filesystem::path module_path; - for (const auto& entry : std::filesystem::recursive_directory_iterator(dir)) - { - if (!std::filesystem::is_regular_file(entry)) - { - continue; - } - - if (entry.path().extension() == "." + module_file_extension) - { - module_path = entry; - } - - if (entry.path().extension() == module_binary_extension) - { - _binary_path = entry; - } - } - - std::ifstream module_fs(module_path); - json module_j = json::parse(module_fs); - _info = module_j; - - if (_enabled.GetValue()) - { - _env->LoadModule(_binary_path); - } - else - { - _env->UnloadModule(_binary_path); - } + _module = std::make_shared(_binary_path, env->GetFactory()); } virtual void operator()() noexcept { - const std::string& name = _binary_path.filename().replace_extension("").string(); - const std::string& version = _info.Version; - const std::string& author = _info.Author; + const std::string& name = _module->GetName(); + const std::string& version = "Version: " + _module->GetVersion(); + const std::string& author = _module->GetAuthor(); ImGui::TableNextColumn(); @@ -92,17 +49,18 @@ class ModuleView : public Widget ImGui::BeginHorizontal(("module_" + name).c_str()); widgets::Text{name}.SetFontSize(20.f)(); - auto posX = - (ImGui::GetCursorPosX() + ImGui::GetColumnWidth() - - ImGui::CalcTextSize(author.length() > ("Version: " + version).length() ? author.c_str() - : ("Version: " + version).c_str()) - .x + - -ImGui::GetScrollX() - 10 * ImGui::GetStyle().ItemSpacing.x); - if (posX > ImGui::GetCursorPosX()) ImGui::SetCursorPosX(posX); + auto pos_x = (ImGui::GetCursorPosX() + ImGui::GetColumnWidth() - + ImGui::CalcTextSize(author.length() > version.length() ? author.c_str() : version.c_str()).x + + -ImGui::GetScrollX() - 10 * ImGui::GetStyle().ItemSpacing.x); + + if (pos_x > ImGui::GetCursorPosX()) + { + ImGui::SetCursorPosX(pos_x); + } ImGui::BeginVertical("version/author"); constexpr Colour version_author_colour{150, 150, 150}; - widgets::Text{"Version: " + version}.SetFontSize(20.f).SetColour(version_author_colour)(); + widgets::Text{version}.SetFontSize(20.f).SetColour(version_author_colour)(); widgets::Text{author}.SetFontSize(18.f).SetColour(version_author_colour)(); ImGui::EndVertical(); ImGui::EndHorizontal(); @@ -111,19 +69,18 @@ class ModuleView : public Widget { if (_enabled.GetValue()) { - _env->LoadModule(_binary_path); + _module->Load(_binary_path); } else { - _env->UnloadModule(_binary_path); + _module->Unload(); } } } private: std::filesystem::path _binary_path; - std::shared_ptr _env; - ModuleInfo _info; + std::shared_ptr _module; widgets::Input _enabled; }; @@ -150,37 +107,10 @@ void ModuleManagerWindow::Draw() std::filesystem::create_directory(_modules_path); const auto filename = - FileExplorer::Load(FileExplorer::GetDocumentsPath(), "Flow Modules (flowmod)", module_file_extension); + FileExplorer::Load(FileExplorer::GetDocumentsPath(), "Flow Module (flowmod)", module_file_extension); - try { - std::ifstream module_fs(filename); - ModuleInfo module_info = json::parse(module_fs); - - const std::string module_file_name = module_info.Name + module_binary_extension; - std::filesystem::path module_binary_path; - for (const auto& entry : std::filesystem::recursive_directory_iterator(filename.parent_path())) - { - if (!std::filesystem::is_regular_file(entry) || entry.path().filename() != module_file_name) - { - continue; - } - - module_binary_path = entry; - break; - } - - std::filesystem::path module_path = _modules_path / module_info.Name; - std::filesystem::create_directory(module_path); - - std::filesystem::copy_file(filename, module_path / filename.filename(), - std::filesystem::copy_options::overwrite_existing); - std::filesystem::copy_file(module_binary_path, module_path / module_binary_path.filename(), - std::filesystem::copy_options::overwrite_existing); - } - catch (const std::exception& e) - { - SPDLOG_ERROR("Caught exception while loading module {}: {}", filename.string(), e.what()); + _widgets[filename.string()] = std::make_shared(filename, _env); } } @@ -201,7 +131,7 @@ void ModuleManagerWindow::Draw() } ImGui::EndHorizontal(); - if (!std::filesystem::exists(_modules_path) || std::filesystem::is_empty(_modules_path)) + if (_widgets.empty()) { return Window::Draw(); } @@ -218,19 +148,9 @@ void ModuleManagerWindow::Draw() ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); - for (const auto& module : std::filesystem::directory_iterator(_modules_path)) + for (const auto& [_, w] : _widgets) { - if (!std::filesystem::is_directory(module)) - { - continue; - } - - const auto& module_name = module.path().string(); - if (!_widgets.contains(module_name)) - { - _widgets[module_name] = std::make_shared(module.path(), _env); - } - (*_widgets.at(module_name))(); + (*w)(); } ImGui::EndTable(); From dd4305039bdaaab525997955da80e103e72798fa Mon Sep 17 00:00:00 2001 From: trigaux Date: Wed, 11 Jun 2025 13:25:10 -0400 Subject: [PATCH 5/5] Update with new function node changes. --- CMakeLists.txt | 2 +- include/flow/ui/Config.hpp | 4 ++-- include/flow/ui/Core.hpp | 22 +++++++--------------- include/flow/ui/windows/GraphWindow.hpp | 2 ++ src/Editor.cpp | 13 ------------- 5 files changed, 12 insertions(+), 31 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ff1e2a5..806f6c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,7 +39,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake") include(cmake/CPM.cmake) -CPMAddPackage("gh:InFlowStructure/flow-core#32a2e54") +CPMAddPackage("gh:InFlowStructure/flow-core#7253c7d") CPMAddPackage("gh:btzy/nativefiledialog-extended#29e3bcb") FetchContent_Declare( diff --git a/include/flow/ui/Config.hpp b/include/flow/ui/Config.hpp index 02b51c3..f02d30e 100644 --- a/include/flow/ui/Config.hpp +++ b/include/flow/ui/Config.hpp @@ -17,9 +17,9 @@ FLOW_UI_NAMESPACE_END * Specialization to avoid trying to cleanup Fonts as they are unowned. */ template<> -struct std::default_delete +struct std::default_delete { - void operator()(FLOW_UI_NAMESPACE::Font*) const {} + void operator()(flow::ui::Font*) const {} }; FLOW_UI_NAMESPACE_START diff --git a/include/flow/ui/Core.hpp b/include/flow/ui/Core.hpp index 784ea54..e97abfb 100644 --- a/include/flow/ui/Core.hpp +++ b/include/flow/ui/Core.hpp @@ -7,20 +7,12 @@ #include -#define FLOW_UI_NAMESPACE FLOW_NAMESPACE::ui -#define FLOW_UI_NAMESPACE_START \ - /** The UI namespace. */ \ - namespace FLOW_UI_NAMESPACE \ - { -#define FLOW_UI_SUBNAMESPACE_START(n) \ - namespace FLOW_UI_NAMESPACE \ - { \ - namespace n \ - { +// clang-format off +#define FLOW_UI_NAMESPACE_START namespace flow::ui { +#define FLOW_UI_SUBNAMESPACE_START(nested) namespace flow::ui { namespace nested { #define FLOW_UI_NAMESPACE_END } -#define FLOW_UI_SUBNAMESPACE_END \ - } \ - } +#define FLOW_UI_SUBNAMESPACE_END } } +// clang-format on FLOW_UI_NAMESPACE_START @@ -29,9 +21,9 @@ class EditorContext; FLOW_UI_NAMESPACE_END template<> -struct std::default_delete +struct std::default_delete { - void operator()(FLOW_UI_NAMESPACE::EditorContext*) const {} + void operator()(flow::ui::EditorContext*) const {} }; FLOW_UI_NAMESPACE_START diff --git a/include/flow/ui/windows/GraphWindow.hpp b/include/flow/ui/windows/GraphWindow.hpp index ef98fda..dfe53f9 100644 --- a/include/flow/ui/windows/GraphWindow.hpp +++ b/include/flow/ui/windows/GraphWindow.hpp @@ -20,6 +20,8 @@ FLOW_UI_NAMESPACE_START +using json = nlohmann::json; + class ContextMenu : public Widget { public: diff --git a/src/Editor.cpp b/src/Editor.cpp index f7cb5f0..7793d0a 100644 --- a/src/Editor.cpp +++ b/src/Editor.cpp @@ -102,14 +102,6 @@ Editor::Editor(const std::string& initial_file) }; } -int return_test_method(const int& i, int& j) { return i * (j += 1); } -void void_test_method(int i, int& j) { j = i * 1000; } -void rvalue_test_method(int& i, int&& j) -{ - i = std::move(j); - return; -} - void Editor::Init(const std::string& initial_file) { _factory->OnNodeClassUnregistered.Bind("Unregister", [&](std::string_view class_name) { @@ -135,11 +127,6 @@ void Editor::Init(const std::string& initial_file) }); _factory->RegisterNodeClass("Editor", "Preview"); - - _factory->RegisterFunction("TEST", "test_method"); - _factory->RegisterFunction("TEST", "void_test_method"); - _factory->RegisterFunction("TEST", "rvalue_test_method"); - _factory->RegisterNodeView(); _factory->RegisterInputType(false);