From a6dbc90b78d9622d607c3f3e768f9d723fe3a0ac Mon Sep 17 00:00:00 2001 From: Anthony Date: Wed, 24 Apr 2024 14:15:57 +0200 Subject: [PATCH] more separation between node UI logic and editor, working Studio format support --- story-editor/CMakeLists.txt | 42 ++--- story-editor/src/i_story_manager.h | 4 +- story-editor/src/importers/pack_archive.cpp | 131 ++++++++++++++- story-editor/src/importers/pack_archive.h | 3 + story-editor/src/main_window.cpp | 118 +------------- story-editor/src/main_window.h | 4 +- story-editor/src/media_converter.cpp | 98 +++++++++++ .../base_node_widget.cpp} | 44 ++--- .../base_node_widget.h} | 50 ++---- .../src/{ => node_editor}/media_node.cpp | 22 ++- .../src/{ => node_editor}/media_node.h | 15 +- .../{ => node_editor}/node_editor_window.cpp | 154 ++++++++---------- .../{ => node_editor}/node_editor_window.h | 35 ++-- story-editor/src/node_engine/base_node.cpp | 70 ++++++++ story-editor/src/node_engine/base_node.h | 55 +++++++ .../src/{ => node_engine}/connection.cpp | 13 +- .../src/{ => node_engine}/connection.h | 8 +- story-editor/src/properties_window.cpp | 4 +- story-editor/src/properties_window.h | 6 +- 19 files changed, 536 insertions(+), 340 deletions(-) rename story-editor/src/{base_node.cpp => node_editor/base_node_widget.cpp} (62%) rename story-editor/src/{base_node.h => node_editor/base_node_widget.h} (79%) rename story-editor/src/{ => node_editor}/media_node.cpp (94%) rename story-editor/src/{ => node_editor}/media_node.h (79%) rename story-editor/src/{ => node_editor}/node_editor_window.cpp (79%) rename story-editor/src/{ => node_editor}/node_editor_window.h (72%) create mode 100644 story-editor/src/node_engine/base_node.cpp create mode 100644 story-editor/src/node_engine/base_node.h rename story-editor/src/{ => node_engine}/connection.cpp (51%) rename story-editor/src/{ => node_engine}/connection.h (89%) diff --git a/story-editor/CMakeLists.txt b/story-editor/CMakeLists.txt index 5e965ad..724d3d0 100644 --- a/story-editor/CMakeLists.txt +++ b/story-editor/CMakeLists.txt @@ -90,6 +90,7 @@ set(BUILD_SHARED_LIBS TRUE) set(SDL_STATIC TRUE) FetchContent_MakeAvailable(sdl3) include_directories(${sdl3_SOURCE_DIR}/include) +# add_subdirectory(${sdl3_SOURCE_DIR}) # ========================================================================================================================= # SDL3-Image @@ -127,20 +128,25 @@ set(SRCS src/main_window.cpp src/main_window.h - src/node_editor_window.cpp - src/node_editor_window.h - src/library_window.cpp src/library_window.h - src/media_node.h - src/media_node.cpp src/platform_folders.cpp src/platform_folders.h - src/base_node.h - src/base_node.cpp + src/node_engine/base_node.h + src/node_engine/base_node.cpp + src/node_engine/connection.cpp + src/node_engine/connection.h + + + src/node_editor/media_node.h + src/node_editor/media_node.cpp + src/node_editor/base_node_widget.h + src/node_editor/base_node_widget.cpp + src/node_editor/node_editor_window.h + src/node_editor/node_editor_window.cpp src/resources_window.cpp src/resources_window.h @@ -154,9 +160,6 @@ set(SRCS src/code_editor.cpp src/code_editor.h - src/connection.cpp - src/connection.h - src/media_converter.cpp src/media_converter.h @@ -234,6 +237,8 @@ target_include_directories(${STORY_EDITOR_PROJECT} PUBLIC ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/src/importers + ${CMAKE_SOURCE_DIR}/src/node_editor + ${CMAKE_SOURCE_DIR}/src/node_engine ../firmware/library ../firmware/chip32 @@ -271,15 +276,16 @@ target_compile_definitions(${STORY_EDITOR_PROJECT} PUBLIC "$<$:DEB target_link_directories(${STORY_EDITOR_PROJECT} PUBLIC ${sdl3_BINARY_DIR} ${curl_BINARY_DIR} ${CMAKE_BINARY_DIR}/libs/SDL_image) - -set(SDL2_BIN_DIR ${sdl3_BINARY_DIR}) +# On est obligé de passer par une variable pour injecter +# certaines informations à CPACK +set(SDL_BIN_DIR ${sdl3_BINARY_DIR}) if(UNIX) target_link_libraries(${STORY_EDITOR_PROJECT} pthread OpenGL::GL dl - SDL3 + SDL3::SDL3 SDL3_image libcurl_static OpenSSL::SSL OpenSSL::Crypto @@ -313,21 +319,19 @@ install_files("." FILES "${CMAKE_SOURCE_DIR}/LICENSE") install_files("." FILES "${CMAKE_SOURCE_DIR}/tools/imgui.ini") if(WIN32) - install_files("." FILES "${SDL2_BIN_DIR}/SDL2.dll") + install_files("." FILES "${SDL_BIN_DIR}/SDL3.dll") install_files("." FILES "/usr/lib/gcc/x86_64-w64-mingw32/10-posix/libstdc++-6.dll") install_files("." FILES "/usr/x86_64-w64-mingw32/lib/libwinpthread-1.dll") install_files("." FILES "/usr/lib/gcc/x86_64-w64-mingw32/10-posix/libgcc_s_seh-1.dll") -endif() -# Personnaliser l'icône pour les installateurs Windows -if(WIN32) - set(CPACK_NSIS_MUI_ICON "${CMAKE_SOURCE_DIR}/story-editor-logo.ico") + # Personnaliser l'icône pour les installateurs Windows + set(CPACK_NSIS_MUI_ICON "${CMAKE_SOURCE_DIR}/story-editor-logo.ico") endif() if (APPLE) set(CPACK_GENERATOR "DragNDrop") set(MACOSX_BUNDLE_INFO_PLIST ${PROJECT_SOURCE_DIR}/bundle.plist.in) - install_files("." FILES "${SDL2_BIN_DIR}/libSDL2-2.0.0.dylib") + install_files("." FILES "${SDL_BIN_DIR}/libSDL2-2.0.0.dylib") endif() include(CPack) diff --git a/story-editor/src/i_story_manager.h b/story-editor/src/i_story_manager.h index e217daa..16d7adc 100644 --- a/story-editor/src/i_story_manager.h +++ b/story-editor/src/i_story_manager.h @@ -46,8 +46,8 @@ public: // Node interaction virtual void Build() = 0; - virtual std::list> GetNodeConnections(unsigned long nodeId) = 0; - virtual std::string GetNodeEntryLabel(unsigned long nodeId) = 0; + virtual std::list> GetNodeConnections(const std::string &nodeId) = 0; + virtual std::string GetNodeEntryLabel(const std::string &nodeId) = 0; virtual void Play() = 0; virtual void Ok() = 0; virtual void Pause() = 0; diff --git a/story-editor/src/importers/pack_archive.cpp b/story-editor/src/importers/pack_archive.cpp index b181471..07308b9 100644 --- a/story-editor/src/importers/pack_archive.cpp +++ b/story-editor/src/importers/pack_archive.cpp @@ -1,8 +1,3 @@ -#include "pack_archive.h" -#include "ni_parser.h" -#include "json.hpp" -#include "serializers.h" - #include #include #include @@ -10,6 +5,15 @@ #include #include + +#include "pack_archive.h" +#include "ni_parser.h" +#include "json.hpp" +#include "serializers.h" +#include "story_project.h" +#include "resource_manager.h" +#include "uuid.h" + PackArchive::PackArchive() { @@ -411,6 +415,123 @@ std::string PackArchive::OpenImage(const std::string &fileName) return f; } +bool PackArchive::ImportStudioFormat(const std::string &fileName, const std::string &outputDir) +{ + auto uuid = UUID().String(); + std::string basePath = outputDir + "/" + uuid; + Unzip(fileName, basePath); + + try + { + + // STUDIO format + std::ifstream f(basePath + "/story.json"); + nlohmann::json j = nlohmann::json::parse(f); + StoryProject proj; + ResourceManager res; + nlohmann::json model; + + if (j.contains("title")) + { + proj.New(uuid, outputDir); + proj.SetName(j["title"].get()); + + // Create resources, scan asset files + std::filesystem::path directoryPath(basePath + "/assets"); + if (std::filesystem::exists(directoryPath) && std::filesystem::is_directory(directoryPath)) + { + for (const auto& entry : std::filesystem::directory_iterator(directoryPath)) + { + if (std::filesystem::is_regular_file(entry.path())) + { + // Si c'est un sous-répertoire, récursivement scanner le contenu + auto rData = std::make_shared(); + + rData->file = entry.path().filename(); + rData->type = ResourceManager::ExtentionInfo(entry.path().extension(), 1); + rData->format = ResourceManager::ExtentionInfo(entry.path().extension(), 0); + res.Add(rData); + } + } + } + + // Key: actionNode, value: Stage UUID + std::map stageActionLink; + + nlohmann::json jnodes = nlohmann::json::array(); + for (const auto & n : j["stageNodes"]) + { + nlohmann::json node; + + auto node_uuid = n["uuid"].get(); + node["uuid"] = node_uuid; + node["type"] = "media-node"; + node["position"] = n["position"]; + + nlohmann::json internalData; + auto img = n["image"]; + internalData["image"] = img.is_string() ? img.get() : ""; + auto audio = n["audio"]; + internalData["sound"] = audio.is_string() ? audio.get() : ""; + + node["internal-data"] = internalData; + + stageActionLink[n["okTransition"]["actionNode"]] = node_uuid; +/* + "okTransition":{ + "actionNode":"19d7328f-d0d2-4443-a7a2-25270dafe52c", + "optionIndex":0 + }, + */ + + jnodes.push_back(node); + } + + model["nodes"] = jnodes; + + nlohmann::json connections = nlohmann::json::array(); + + for (const auto & n : j["actionNodes"]) + { + std::string action_node_uuid = n["id"].get(); // le champs est "id" et non pas "uuid", pénible + + if (stageActionLink.count(action_node_uuid) > 0) + { + + int i = 0; + for (const auto & m : n["options"]) + { + nlohmann::json c; + + c["outNodeId"] = stageActionLink[action_node_uuid]; + c["outPortIndex"] = i; + c["inNodeId"] = m; // On prend le stage node; + c["inPortIndex"] = 0; + + i++; + connections.push_back(c); + } + } + else + { + std::cout << "ActionNode UUID not found" << std::endl; + } + } + + model["connections"] = connections; + + // Save on disk + proj.Save(model, res); + } + } + catch(std::exception &e) + { + std::cout << e.what() << std::endl; + } + + return false; +} + std::string PackArchive::GetImage(const std::string &fileName) { //"C8B39950DE174EAA8E852A07FC468267/rf/000/05FB5530" diff --git a/story-editor/src/importers/pack_archive.h b/story-editor/src/importers/pack_archive.h index f8a960d..688933e 100644 --- a/story-editor/src/importers/pack_archive.h +++ b/story-editor/src/importers/pack_archive.h @@ -14,6 +14,9 @@ public: bool Load(const std::string &filePath); std::string OpenImage(const std::string &fileName); + + bool ImportStudioFormat(const std::string &fileName, const std::string &outputDir); + std::string CurrentImage(); std::string CurrentSound(); std::string CurrentSoundName(); diff --git a/story-editor/src/main_window.cpp b/story-editor/src/main_window.cpp index c76c618..69a39a6 100644 --- a/story-editor/src/main_window.cpp +++ b/story-editor/src/main_window.cpp @@ -235,8 +235,6 @@ void MainWindow::DrawStatusBar() ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoDocking; ImGui::Begin("StatusBar", nullptr, windowFlags); - - if (true) { float dy = ImGui::GetFontSize() * 0.15f; @@ -657,117 +655,7 @@ void MainWindow::ImportProject(const std::string &fileName, int format) { PackArchive archive; - Log("Decompressing " + fileName); - auto uuid = UUID().String(); - - std::string basePath = m_libraryManager.LibraryPath() + "/" + uuid; - - archive.Unzip(fileName, basePath); - - // Ok try to convert the archive into our format - - try - { - - // STUDIO format - std::ifstream f(basePath + "/story.json"); - nlohmann::json j = nlohmann::json::parse(f); - StoryProject proj; - ResourceManager res; - nlohmann::json model; - - if (j.contains("title")) - { - proj.New(uuid, m_libraryManager.LibraryPath()); - proj.SetName(j["title"].get()); - - // Create resources, scan asset files - std::filesystem::path directoryPath(basePath + "/assets"); - if (std::filesystem::exists(directoryPath) && std::filesystem::is_directory(directoryPath)) - { - for (const auto& entry : std::filesystem::directory_iterator(directoryPath)) - { - if (std::filesystem::is_regular_file(entry.path())) - { - // Si c'est un sous-répertoire, récursivement scanner le contenu - std::string asset = entry.path().filename(); - auto rData = std::make_shared(); - - rData->type = ResourceManager::ExtentionInfo(entry.path().extension(), 1); - rData->format = ResourceManager::ExtentionInfo(entry.path().extension(), 0); - res.Add(rData); - } - } - } - - // Create model - - int ids = 1; - nlohmann::json nodes = nlohmann::json::array(); - for (const auto & n : j["stageNodes"]) - { - nlohmann::json node; - node["id"] = ids++; - node["uuid"] = n["uuid"].get(); - node["type"] = "media-node"; - - auto type = n["type"].get(); - - int outPortCount = 1; - int inPortCount = 1; - - if (type == "stage") { - outPortCount = 1; - inPortCount = 1; - } - - node["outPortCount"] = outPortCount; - node["inPortCount"] = inPortCount; - node["position"] = n["position"]; - - nlohmann::json internalData; - auto img = n["image"]; - internalData["image"] = img.is_string() ? img.get() : ""; - auto audio = n["audio"]; - internalData["sound"] = audio.is_string() ? audio.get() : ""; - - node["internal-data"] = internalData; - nodes.push_back(node); - } - - model["nodes"] = nodes; - - // Save links - nlohmann::json connections = nlohmann::json::array(); - - /* - for (const auto& linkInfo : m_links) - { - - nlohmann::json c; - - Connection cnx = LinkToModel(linkInfo->ed_link->InputId, linkInfo->ed_link->OutputId); - - c["outNodeId"] = cnx.outNodeId; - c["outPortIndex"] = cnx.outPortIndex; - c["inNodeId"] = cnx.inNodeId; - c["inPortIndex"] = cnx.inPortIndex; - - connections.push_back(c); - }*/ - - model["connections"] = connections; - - - } - - // Save on disk - proj.Save(model, res); - } - catch(std::exception &e) - { - std::cout << e.what() << std::endl; - } + archive.ImportStudioFormat(fileName, m_libraryManager.LibraryPath()); } @@ -923,12 +811,12 @@ void MainWindow::Build() ConvertResources(); } -std::string MainWindow::GetNodeEntryLabel(unsigned long nodeId) +std::string MainWindow::GetNodeEntryLabel(const std::string &nodeId) { return m_nodeEditorWindow.GetNodeEntryLabel(nodeId); } -std::list> MainWindow::GetNodeConnections(unsigned long nodeId) +std::list> MainWindow::GetNodeConnections(const std::string &nodeId) { return m_nodeEditorWindow.GetNodeConnections(nodeId); } diff --git a/story-editor/src/main_window.h b/story-editor/src/main_window.h index 7c52912..2efe504 100644 --- a/story-editor/src/main_window.h +++ b/story-editor/src/main_window.h @@ -135,8 +135,8 @@ private: virtual std::pair Resources() override; virtual void DeleteResource(FilterIterator &it) override; virtual void Build() override; - virtual std::list> GetNodeConnections(unsigned long nodeId) override; - virtual std::string GetNodeEntryLabel(unsigned long nodeId) override; + virtual std::list> GetNodeConnections(const std::string &nodeId) override; + virtual std::string GetNodeEntryLabel(const std::string &nodeId) override; virtual void Play() override; virtual void Ok() override; virtual void Pause() override; diff --git a/story-editor/src/media_converter.cpp b/story-editor/src/media_converter.cpp index c1ca4b4..20b23cd 100644 --- a/story-editor/src/media_converter.cpp +++ b/story-editor/src/media_converter.cpp @@ -157,3 +157,101 @@ int MediaConverter::Mp3ToWav(const std::string &inputFileName, const std::string return cSuccess; } + +/* + +OGG TO WAV + +#define STB_VORBIS_HEADER_ONLY +#include "stb_vorbis.c" + +#include +#include + +// Function to read the entire contents of a file into memory +unsigned char* read_entire_file(const char* filename, int* length) { + FILE* f = fopen(filename, "rb"); + if (!f) return NULL; + fseek(f, 0, SEEK_END); + long size = ftell(f); + fseek(f, 0, SEEK_SET); + unsigned char* buffer = (unsigned char*)malloc(size); + if (!buffer) { + fclose(f); + return NULL; + } + fread(buffer, 1, size, f); + fclose(f); + if (length) *length = (int)size; + return buffer; +} + +int main(int argc, char** argv) { + if (argc != 3) { + printf("Usage: %s input.ogg output.wav\n", argv[0]); + return 1; + } + + const char* input_filename = argv[1]; + const char* output_filename = argv[2]; + + int ogg_length; + unsigned char* ogg_data = read_entire_file(input_filename, &ogg_length); + if (!ogg_data) { + printf("Failed to read input file %s\n", input_filename); + return 1; + } + + int channels, sample_rate; + short* samples = stb_vorbis_decode_memory(ogg_data, ogg_length, &channels, &sample_rate); + if (!samples) { + printf("Failed to decode OGG file %s\n", input_filename); + free(ogg_data); + return 1; + } + + // Write WAV file header + FILE* wav_file = fopen(output_filename, "wb"); + if (!wav_file) { + printf("Failed to create output file %s\n", output_filename); + free(ogg_data); + free(samples); + return 1; + } + fwrite("RIFF", 1, 4, wav_file); + int total_size = 36 + channels * (sample_rate * sizeof(short)); + fwrite(&total_size, 4, 1, wav_file); + fwrite("WAVEfmt ", 1, 8, wav_file); + int format_size = 16; + fwrite(&format_size, 4, 1, wav_file); + short format_type = 1; // PCM + fwrite(&format_type, 2, 1, wav_file); + fwrite(&channels, 2, 1, wav_file); + fwrite(&sample_rate, 4, 1, wav_file); + int byte_rate = sample_rate * channels * sizeof(short); + fwrite(&byte_rate, 4, 1, wav_file); + short block_align = channels * sizeof(short); + fwrite(&block_align, 2, 1, wav_file); + short bits_per_sample = 16; + fwrite(&bits_per_sample, 2, 1, wav_file); + fwrite("data", 1, 4, wav_file); + int data_size = channels * (sample_rate * sizeof(short)); + fwrite(&data_size, 4, 1, wav_file); + + // Write sample data + fwrite(samples, sizeof(short), sample_rate * channels, wav_file); + + fclose(wav_file); + + free(ogg_data); + free(samples); + + printf("Conversion complete. WAV file saved as %s\n", output_filename); + + return 0; +} + + + +*/ + diff --git a/story-editor/src/base_node.cpp b/story-editor/src/node_editor/base_node_widget.cpp similarity index 62% rename from story-editor/src/base_node.cpp rename to story-editor/src/node_editor/base_node_widget.cpp index a824c1a..c36517b 100644 --- a/story-editor/src/base_node.cpp +++ b/story-editor/src/node_editor/base_node_widget.cpp @@ -1,25 +1,24 @@ -#include "base_node.h" +#include "base_node_widget.h" #include "uuid.h" #include "IconsMaterialDesignIcons.h" -unsigned long BaseNode::s_nextId = 1; +unsigned long BaseNodeWidget::s_nextId = 1; -BaseNode::BaseNode(const std::string &title, IStoryManager &proj) - : m_story(proj) +BaseNodeWidget::BaseNodeWidget(const std::string &type, IStoryManager &proj) + : BaseNode(type) + , m_story(proj) { // m_id = UUID().String(); - - m_id = -1; // Story Project Node ID - m_node = std::make_unique(GetNextId(), title.c_str()); // ImGui internal ID + m_node = std::make_unique(GetNextId(), ""); // ImGui internal ID } -void BaseNode::AddInput() +void BaseNodeWidget::AddInput() { m_node->Inputs.emplace_back(GetNextId(), "", PinType::Flow); } -void BaseNode::AddOutputs(int num) +void BaseNodeWidget::AddOutputs(int num) { for (int i = 0; i < num; i++) { @@ -27,7 +26,7 @@ void BaseNode::AddOutputs(int num) } } -void BaseNode::SetOutputs(uint32_t num) +void BaseNodeWidget::SetOutputs(uint32_t num) { if (num > Outputs()) { @@ -42,35 +41,38 @@ void BaseNode::SetOutputs(uint32_t num) } } -void BaseNode::DeleteOutput() +void BaseNodeWidget::DeleteOutput() { m_node->Outputs.pop_back(); } -void BaseNode::SetPosition(float x, float y) + +void BaseNodeWidget::Initialize() { - m_pos.x = x; - m_pos.y = y; - m_firstFrame = true; + m_firstFrame = true; + } -void BaseNode::FrameStart() + +void BaseNodeWidget::FrameStart() { ed::BeginNode(m_node->ID); if (m_firstFrame) { - ed::SetNodePosition(m_node->ID, ImVec2(m_pos.x, m_pos.y)); + // Use the parent node position, the one saved in the JSON project + // FIXME: find a better way to do that? + ed::SetNodePosition(m_node->ID, ImVec2(BaseNode::GetX(), BaseNode::GetY())); } m_firstFrame = false; } -void BaseNode::FrameEnd() +void BaseNodeWidget::FrameEnd() { ed::EndNode(); } -void BaseNode::DrawPins() +void BaseNodeWidget::DrawPins() { static const char *str = "#1 >"; static float textWidth = ImGui::CalcTextSize(str).x; @@ -94,13 +96,13 @@ void BaseNode::DrawPins() } } -float BaseNode::GetX() const +float BaseNodeWidget::GetX() const { auto pos = GetNodePosition(m_node->ID); return pos.x; } -float BaseNode::GetY() const +float BaseNodeWidget::GetY() const { auto pos = GetNodePosition(m_node->ID); return pos.y; diff --git a/story-editor/src/base_node.h b/story-editor/src/node_editor/base_node_widget.h similarity index 79% rename from story-editor/src/base_node.h rename to story-editor/src/node_editor/base_node_widget.h index a989385..54caf1c 100644 --- a/story-editor/src/base_node.h +++ b/story-editor/src/node_editor/base_node_widget.h @@ -2,12 +2,12 @@ #include #include -#include "json.hpp" #include #include #include "json.hpp" #include "i_story_manager.h" +#include "base_node.h" #include namespace ed = ax::NodeEditor; @@ -91,8 +91,11 @@ struct Link } }; - -class BaseNode +/** + * @brief Basically a wrapper class around ImGuiNodeEditor Node structure + * + */ +class BaseNodeWidget : public BaseNode { public: struct NodePosition @@ -101,8 +104,9 @@ public: float y; }; - BaseNode(const std::string &title, IStoryManager &proj); + BaseNodeWidget(const std::string &type, IStoryManager &proj); + virtual void Initialize(); virtual void Draw() = 0; virtual void DrawProperties() = 0; @@ -110,45 +114,20 @@ public: virtual std::string Build() = 0; virtual std::string GetEntryLabel() = 0; - void SetPosition(float x, float y); void FrameStart(); void FrameEnd(); - void DrawPins(); - float GetX() const; - float GetY() const; + virtual float GetX() const; + virtual float GetY() const; + uint32_t Inputs() const { return m_node->Inputs.size(); } uint32_t Outputs() const { return m_node->Outputs.size(); } - void SetType(const std::string &type) - { - m_type = type; - } - - std::string GetType() const - { - return m_type; - } - - void SetId(unsigned long id) { m_id = id; } - unsigned long GetId() const { return m_id; } unsigned long GetInternalId() const { return m_node->ID.Get(); } - void SeTitle(const std::string &title) { m_title = title; } - std::string GetTitle() const { return m_title; } - - virtual void FromJson(const nlohmann::json &) = 0; - virtual void ToJson(nlohmann::json &) = 0; - - virtual nlohmann::json ToJson() const { - nlohmann::json j; - - j["type"] = m_type; - return j; - } static unsigned long GetNextId() { @@ -227,16 +206,9 @@ private: std::unique_ptr m_node; - std::string m_title{"Base node"}; - std::string m_type; - unsigned long m_id; - NodePosition m_pos; bool m_firstFrame{true}; static unsigned long s_nextId; - - - }; diff --git a/story-editor/src/media_node.cpp b/story-editor/src/node_editor/media_node.cpp similarity index 94% rename from story-editor/src/media_node.cpp rename to story-editor/src/node_editor/media_node.cpp index eb5043b..1064ced 100644 --- a/story-editor/src/media_node.cpp +++ b/story-editor/src/node_editor/media_node.cpp @@ -7,7 +7,7 @@ namespace ed = ax::NodeEditor; #include "story_project.h" MediaNode::MediaNode(const std::string &title, IStoryManager &proj) - : BaseNode(title, proj) + : BaseNodeWidget(title, proj) , m_story(proj) { // Create defaut one input and one output @@ -22,7 +22,7 @@ MediaNode::MediaNode(const std::string &title, IStoryManager &proj) void MediaNode::Draw() { - BaseNode::FrameStart(); + BaseNodeWidget::FrameStart(); static ImGuiTableFlags flags = ImGuiTableFlags_Borders | @@ -69,11 +69,11 @@ void MediaNode::Draw() uint32_t counter = Outputs(); float spacing = ImGui::GetStyle().ItemInnerSpacing.x; ImGui::PushButtonRepeat(true); - std::string leftSingle = "##left" + std::to_string(GetId()); + std::string leftSingle = "##left" + GetId(); if (ImGui::ArrowButton(leftSingle.c_str(), ImGuiDir_Left)) { if (counter > 1) counter--; } ImGui::SameLine(0.0f, spacing); - std::string rightSingle = "##right" + std::to_string(GetId()); + std::string rightSingle = "##right" + GetId(); if (ImGui::ArrowButton(rightSingle.c_str(), ImGuiDir_Right)) { counter++; @@ -86,7 +86,7 @@ void MediaNode::Draw() DrawPins(); - BaseNode::FrameEnd(); + BaseNodeWidget::FrameEnd(); } @@ -96,18 +96,22 @@ void MediaNode::Draw() "image": "fairy.png", "sound": "la_fee_luminelle.mp3" }, - */ -void MediaNode::FromJson(const nlohmann::json &j) +void MediaNode::Initialize() { + BaseNodeWidget::Initialize(); + nlohmann::json j = GetInternalData(); SetImage(j["image"].get()); SetSound(j["sound"].get()); } -void MediaNode::ToJson(nlohmann::json &j) +void MediaNode::StoreInternalData() { + nlohmann::json j; j["image"] = m_image.name; j["sound"] = m_soundName; + + SetInternalData(j); } void MediaNode::DrawProperties() @@ -186,12 +190,14 @@ void MediaNode::SetImage(const std::string &f) { m_image.name = f; m_image.Load(m_story.BuildFullAssetsPath(f)); + StoreInternalData(); } void MediaNode::SetSound(const std::string &f) { m_soundName = f; m_soundPath = m_story.BuildFullAssetsPath(m_soundName); + StoreInternalData(); } diff --git a/story-editor/src/media_node.h b/story-editor/src/node_editor/media_node.h similarity index 79% rename from story-editor/src/media_node.h rename to story-editor/src/node_editor/media_node.h index e05b445..74b9243 100644 --- a/story-editor/src/media_node.h +++ b/story-editor/src/node_editor/media_node.h @@ -5,35 +5,36 @@ #include #include -#include "base_node.h" +#include "base_node_widget.h" #include "i_story_manager.h" #include "gui.h" #include -class MediaNode : public BaseNode +class MediaNode : public BaseNodeWidget { public: MediaNode(const std::string &title, IStoryManager &proj); void Draw() override; - virtual void FromJson(const nlohmann::json &j) override; - virtual void ToJson(nlohmann::json &j) override; virtual void DrawProperties() override; virtual std::string Build() override; virtual std::string GetEntryLabel() override; virtual std::string GenerateConstants() override; + + virtual void Initialize() override; + + private: IStoryManager &m_story; Gui::Image m_image; std::string m_soundName; std::string m_soundPath; - - std::string m_id; - std::string m_buttonUniqueName; + void SetImage(const std::string &f); void SetSound(const std::string &f); std::string ChoiceLabel() const; + void StoreInternalData(); }; diff --git a/story-editor/src/node_editor_window.cpp b/story-editor/src/node_editor/node_editor_window.cpp similarity index 79% rename from story-editor/src/node_editor_window.cpp rename to story-editor/src/node_editor/node_editor_window.cpp index d10ede1..381b465 100644 --- a/story-editor/src/node_editor_window.cpp +++ b/story-editor/src/node_editor/node_editor_window.cpp @@ -11,6 +11,7 @@ #include "media_node.h" #include "gui.h" +#include "uuid.h" #include // for std::runtime_error #define JSON_ASSERT(x) \ @@ -45,59 +46,15 @@ void NodeEditorWindow::Initialize() void NodeEditorWindow::Clear() { m_nodes.clear(); - m_ids.clear(); } - - -void NodeEditorWindow::LoadNode(const nlohmann::json &nodeJson) +std::string NodeEditorWindow::GenerateNodeId() { - try - { - int restoredNodeId = nodeJson["id"].get(); - nlohmann::json internalDataJson = nodeJson["internal-data"]; - std::string type = nodeJson["type"].get(); - - auto n = createNode(type, "", m_story); - if (n) - { - n->SetType(type); // FIXME: set type in createNode factory? - n->SetId(restoredNodeId); - nlohmann::json posJson = nodeJson["position"]; - n->SetOutputs(nodeJson["outPortCount"].get()); - n->SetPosition(posJson["x"].get(), posJson["y"].get()); - n->FromJson(internalDataJson); - - m_ids.insert(restoredNodeId); - - m_nodes.push_back(n); - } - else - { - throw std::logic_error(std::string("No registered model with name ") + type); - } - } - catch (std::exception& e) - { - std::cout << "ERROR: " << e.what() << std::endl; - } - -} - -int NodeEditorWindow::GenerateNodeId() -{ - int max = 1; - if (m_ids.size() > 0) - { - auto max = *m_ids.rbegin(); - max++; - m_ids.insert(max); - } - return max; + return UUID().String(); } -ed::PinId NodeEditorWindow::GetInputPin(unsigned long modelNodeId, int pinIndex) +ed::PinId NodeEditorWindow::GetInputPin(const std::string &modelNodeId, int pinIndex) { ed::PinId id = 0; @@ -106,18 +63,19 @@ ed::PinId NodeEditorWindow::GetInputPin(unsigned long modelNodeId, int pinIndex) if (n->GetId() == modelNodeId) { id = n->GetInputPinAt(pinIndex); + break; } } if (id.Get() == 0) { - std::cout << "Invalid Id, input pin not found" << std::endl; + std::cout << "Invalid Id: " << modelNodeId << " input pin: " << pinIndex <<" not found" << std::endl; } return id; } -ed::PinId NodeEditorWindow::GetOutputPin(unsigned long modelNodeId, int pinIndex) +ed::PinId NodeEditorWindow::GetOutputPin(const std::string &modelNodeId, int pinIndex) { ed::PinId id = 0; @@ -126,12 +84,13 @@ ed::PinId NodeEditorWindow::GetOutputPin(unsigned long modelNodeId, int pinIndex if (n->GetId() == modelNodeId) { id = n->GetOutputPinAt(pinIndex); + break; } } if (id.Get() == 0) { - std::cout << "Invalid Id, output pin not found" << std::endl; + std::cout << "Invalid Id: " << modelNodeId << " output pin: " << pinIndex <<" not found" << std::endl; } return id; @@ -141,15 +100,27 @@ void NodeEditorWindow::Load(const nlohmann::json &model) { try { - nlohmann::json nodesJsonArray = model["nodes"]; - BaseNode::InitId(); + BaseNodeWidget::InitId(); m_nodes.clear(); m_links.clear(); for (auto& element : nodesJsonArray) { - LoadNode(element); + + std::string type = element["type"].get(); + + auto n = createNode(type, m_story); + if (n) + { + n->FromJson(element); + n->Initialize(); + m_nodes.push_back(n); + } + else + { + throw std::logic_error(std::string("No registered model with name ") + type); + } } std::cout << model.dump(4) << std::endl; @@ -160,20 +131,47 @@ void NodeEditorWindow::Load(const nlohmann::json &model) { nlohmann::json connectionJsonArray = model["connections"]; + // key: node UUID, value: output counts + std::map outputCounts; + for (auto& connection : connectionJsonArray) { Connection model = connection.get(); + + + if (outputCounts.count(model.outNodeId) > 0) + { + outputCounts[model.outNodeId]++; + } + else + { + outputCounts[model.outNodeId] = 1; + } + + // Adjust the number of outputs for all nodes + for (auto n : m_nodes) + { + if (outputCounts.count(n->GetId()) > 0) + { + n->SetOutputs(outputCounts[n->GetId()]); + } + } + CreateLink(model, GetInputPin(model.inNodeId, model.inPortIndex), GetOutputPin(model.outNodeId, model.outPortIndex)); + + } + + } m_loaded = true; } catch(std::exception &e) { - std::cout << e.what() << std::endl; + std::cout << "(NodeEditorWindow::Load) " << e.what() << std::endl; } } @@ -186,7 +184,7 @@ void NodeEditorWindow::CreateLink(const Connection &model, ed::PinId inId, ed::P *conn->model = model; // ImGui stuff for links - conn->ed_link->Id = BaseNode::GetNextId(); + conn->ed_link->Id = BaseNodeWidget::GetNextId(); conn->ed_link->InputId = inId; conn->ed_link->OutputId = outId; @@ -202,23 +200,7 @@ void NodeEditorWindow::Save(nlohmann::json &model) nlohmann::json nodes = nlohmann::json::array(); for (const auto & n : m_nodes) { - nlohmann::json node; - node["id"] = n->GetId(); - node["type"] = n->GetType(); - node["outPortCount"] = n->Outputs(); - node["inPortCount"] = n->Inputs(); - - nlohmann::json position; - position["x"] = n->GetX(); - position["y"] = n->GetY(); - - nlohmann::json internalData; - - n->ToJson(internalData); - - node["position"] = position; - node["internal-data"] = internalData; - nodes.push_back(node); + nodes.push_back(n->ToJson()); } model["nodes"] = nodes; @@ -227,7 +209,6 @@ void NodeEditorWindow::Save(nlohmann::json &model) nlohmann::json connections = nlohmann::json::array(); for (const auto& linkInfo : m_links) { - nlohmann::json c; Connection cnx = LinkToModel(linkInfo->ed_link->InputId, linkInfo->ed_link->OutputId); @@ -266,9 +247,9 @@ Connection NodeEditorWindow::LinkToModel(ed::PinId InputId, ed::PinId OutputId) return c; } -uint32_t NodeEditorWindow::FindFirstNode() const +std::string NodeEditorWindow::FindFirstNode() const { - uint32_t id = 0; + std::string id; // First node is the one without connection on its input port @@ -286,7 +267,7 @@ uint32_t NodeEditorWindow::FindFirstNode() const if (!foundConnection) { id = n->GetId(); - m_story.Log("First node is: " + std::to_string(id)); + m_story.Log("First node is: " + id); break; } } @@ -302,7 +283,7 @@ std::string NodeEditorWindow::Build() std::stringstream chip32; - uint32_t firstNode = FindFirstNode(); + std::string firstNode = FindFirstNode(); code << "\tjump " << GetNodeEntryLabel(firstNode) << "\r\n"; @@ -321,13 +302,13 @@ std::string NodeEditorWindow::Build() return code.str(); } -std::list> NodeEditorWindow::GetNodeConnections(unsigned long nodeId) +std::list> NodeEditorWindow::GetNodeConnections(const std::string &nodeId) { std::list> c; ed::SetCurrentEditor(m_context); for (const auto & l : m_links) - { + { if (l->model->outNodeId == nodeId) { c.push_back(l->model); @@ -338,7 +319,7 @@ std::list> NodeEditorWindow::GetNodeConnections(unsi return c; } -std::string NodeEditorWindow::GetNodeEntryLabel(unsigned long nodeId) +std::string NodeEditorWindow::GetNodeEntryLabel(const std::string &nodeId) { std::string label; ed::SetCurrentEditor(m_context); @@ -357,9 +338,9 @@ std::string NodeEditorWindow::GetNodeEntryLabel(unsigned long nodeId) } -std::shared_ptr NodeEditorWindow::GetSelectedNode() +std::shared_ptr NodeEditorWindow::GetSelectedNode() { - std::shared_ptr selected; + std::shared_ptr selected; ed::SetCurrentEditor(m_context); if (ed::GetSelectedObjectCount() > 0) @@ -485,21 +466,16 @@ void NodeEditorWindow::Draw() Node* node = nullptr; if (ImGui::MenuItem("Media Node")) { - auto n = createNode("media-node", "", m_story); + auto n = createNode("media-node", m_story); if (n) { - n->SetType("media-node"); // FIXME: set type in createNode factory? n->SetId(GenerateNodeId()); n->SetPosition(newNodePostion.x, newNodePostion.y); + n->Initialize(); m_nodes.push_back(n); } } - // if (node) - // { - // ed::SetNodePosition(node->ID, newNodePostion); - // } - ImGui::EndPopup(); } diff --git a/story-editor/src/node_editor_window.h b/story-editor/src/node_editor/node_editor_window.h similarity index 72% rename from story-editor/src/node_editor_window.h rename to story-editor/src/node_editor/node_editor_window.h index 40efe66..e225524 100644 --- a/story-editor/src/node_editor_window.h +++ b/story-editor/src/node_editor/node_editor_window.h @@ -5,7 +5,7 @@ #include #include -#include "base_node.h" +#include "base_node_widget.h" #include "window_base.h" #include "i_story_manager.h" #include "json.hpp" @@ -55,10 +55,10 @@ public: void Load(const nlohmann::json &model); void Save(nlohmann::json &model); std::string Build(); - std::list > GetNodeConnections(unsigned long nodeId); - std::string GetNodeEntryLabel(unsigned long nodeId); + std::list > GetNodeConnections(const std::string &nodeId); + std::string GetNodeEntryLabel(const std::string &nodeId); - std::shared_ptr GetSelectedNode(); + std::shared_ptr GetSelectedNode(); private: IStoryManager &m_story; @@ -68,12 +68,10 @@ private: bool m_loaded{false}; // key: Id - std::list> m_nodes; + std::list> m_nodes; std::list> m_links; // List of live links. It is dynamic unless you want to create read-only view over nodes. void ToolbarUI(); - std::set m_ids; - void BuildNode(Node* node) { for (auto& input : node->Inputs) @@ -91,12 +89,12 @@ private: template struct Factory { - static std::shared_ptr create_func(const std::string &title, IStoryManager &proj) { - return std::make_shared(title, proj); + static std::shared_ptr create_func(const std::string &type, IStoryManager &proj) { + return std::make_shared(type, proj); } }; - typedef std::shared_ptr (*GenericCreator)(const std::string &title, IStoryManager &proj); + typedef std::shared_ptr (*GenericCreator)(const std::string &type, IStoryManager &proj); typedef std::map Registry; Registry m_registry; @@ -105,20 +103,23 @@ private: m_registry.insert(typename Registry::value_type(key, Factory::create_func)); } - std::shared_ptr createNode(const std::string& key, const std::string &title, IStoryManager &proj) { + std::shared_ptr createNode(const std::string& key, IStoryManager &proj) { typename Registry::const_iterator i = m_registry.find(key); if (i == m_registry.end()) { throw std::invalid_argument(std::string(__PRETTY_FUNCTION__) + ": key not registered"); } - else return i->second(title, proj); + else + { + return i->second(key, proj); + } } - void LoadNode(const nlohmann::json &nodeJson); - ed::PinId GetInputPin(unsigned long modelNodeId, int pinIndex); - ed::PinId GetOutputPin(unsigned long modelNodeId, int pinIndex); - uint32_t FindFirstNode() const; - int GenerateNodeId(); + + ed::PinId GetInputPin(const std::string &modelNodeId, int pinIndex); + ed::PinId GetOutputPin(const std::string &modelNodeId, int pinIndex); + std::string FindFirstNode() const; + std::string GenerateNodeId(); void CreateLink(const Connection &model, ed::PinId inId, ed::PinId outId); Connection LinkToModel(ed::PinId InputId, ed::PinId OutputId); }; diff --git a/story-editor/src/node_engine/base_node.cpp b/story-editor/src/node_engine/base_node.cpp new file mode 100644 index 0000000..63b06d6 --- /dev/null +++ b/story-editor/src/node_engine/base_node.cpp @@ -0,0 +1,70 @@ +#include "base_node.h" + +#include + +BaseNode::BaseNode(const std::string &type) +{ + m_type = type; +} + +void BaseNode::FromJson(const nlohmann::json &j) +{ + try + { + m_uuid = j["uuid"].get(); + m_internal_data = j["internal-data"]; + m_type = j["type"].get(); + m_title = j.value("title", "Default node"); + nlohmann::json posJson = j["position"]; + SetPosition(posJson["x"].get(), posJson["y"].get()); + } + catch (std::exception& e) + { + std::cout << "ERROR: " << e.what() << std::endl; + } + +} + + +nlohmann::json BaseNode::ToJson() const +{ + nlohmann::json node; + node["uuid"] = GetId(); + node["type"] = GetType(); + + nlohmann::json position; + position["x"] = GetX(); + position["y"] = GetY(); + + node["position"] = position; + node["internal-data"] = m_internal_data; + + return node; +} + + +void BaseNode::SetInternalData(const nlohmann::json &j) +{ + m_internal_data = j; +} + +nlohmann::json BaseNode::GetInternalData() const +{ + return m_internal_data; +} + +void BaseNode::SetPosition(float x, float y) +{ + m_pos.x = x; + m_pos.y = y; +} + +float BaseNode::GetX() const +{ + return m_pos.x; +} + +float BaseNode::GetY() const +{ + return m_pos.y; +} diff --git a/story-editor/src/node_engine/base_node.h b/story-editor/src/node_engine/base_node.h new file mode 100644 index 0000000..9b08913 --- /dev/null +++ b/story-editor/src/node_engine/base_node.h @@ -0,0 +1,55 @@ +#pragma once + +#include +#include +#include "json.hpp" +#include +#include + +#include "json.hpp" + +class BaseNode +{ +public: + struct NodePosition + { + float x; + float y; + }; + + BaseNode(const std::string &type); + + void SetPosition(float x, float y); + + // make this virtual so that graphical node override the behavior + virtual float GetX() const; + virtual float GetY() const; + + std::string GetType() const + { + return m_type; + } + + void SetId(const std::string &id) { m_uuid = id; } + std::string GetId() const { return m_uuid; } + + void SeTitle(const std::string &title) { m_title = title; } + std::string GetTitle() const { return m_title; } + + void FromJson(const nlohmann::json &); + nlohmann::json ToJson() const; + + + void SetInternalData(const nlohmann::json &j); + nlohmann::json GetInternalData() const; + + +private: + std::string m_title{"Default title"}; + std::string m_type; + std::string m_uuid; + NodePosition m_pos; + + nlohmann::json m_internal_data; +}; + diff --git a/story-editor/src/connection.cpp b/story-editor/src/node_engine/connection.cpp similarity index 51% rename from story-editor/src/connection.cpp rename to story-editor/src/node_engine/connection.cpp index 6b5ad8a..266165c 100644 --- a/story-editor/src/connection.cpp +++ b/story-editor/src/node_engine/connection.cpp @@ -2,16 +2,17 @@ void to_json(nlohmann::json &j, const Connection &p) { j = nlohmann::json{ - {"outNodeId", static_cast(p.outNodeId)}, + {"outNodeId", p.outNodeId }, {"outPortIndex", static_cast(p.outPortIndex)}, - {"inNodeId", static_cast(p.inNodeId)}, + {"inNodeId", p.inNodeId}, {"inPortIndex", static_cast(p.inPortIndex)}, }; } void from_json(const nlohmann::json &j, Connection &p) { - j.at("outNodeId").get_to(p.outNodeId); - j.at("outPortIndex").get_to(p.outPortIndex); - j.at("inNodeId").get_to(p.inNodeId); - j.at("inPortIndex").get_to(p.inPortIndex); + + p.outNodeId = j["outNodeId"].get(); + p.inNodeId = j["inNodeId"].get(); + p.outPortIndex = j["outPortIndex"].get(); + p.inPortIndex = j["inPortIndex"].get(); } diff --git a/story-editor/src/connection.h b/story-editor/src/node_engine/connection.h similarity index 89% rename from story-editor/src/connection.h rename to story-editor/src/node_engine/connection.h index b515b61..7c9ddfe 100644 --- a/story-editor/src/connection.h +++ b/story-editor/src/node_engine/connection.h @@ -6,9 +6,7 @@ struct Connection { Connection() - : outNodeId(0) - , outPortIndex(0) - , inNodeId(0) + : outPortIndex(0) , inPortIndex(0) { @@ -18,9 +16,9 @@ struct Connection } - unsigned int outNodeId{0}; + std::string outNodeId; unsigned int outPortIndex{0}; - unsigned int inNodeId{0}; + std::string inNodeId; unsigned int inPortIndex{0}; Connection(const Connection &other){ diff --git a/story-editor/src/properties_window.cpp b/story-editor/src/properties_window.cpp index 94dac2e..5062e4a 100644 --- a/story-editor/src/properties_window.cpp +++ b/story-editor/src/properties_window.cpp @@ -25,14 +25,14 @@ void PropertiesWindow::Draw() if (m_selectedNode) { static char buf1[32] = ""; ImGui::InputText("Title", buf1, 32); - ImGui::Text("Node ID: %lu", m_selectedNode->GetId()); + ImGui::Text("Node ID: %s", m_selectedNode->GetId().data()); m_selectedNode->DrawProperties(); } WindowBase::EndDraw(); } -void PropertiesWindow::SetSelectedNode(std::shared_ptr node) +void PropertiesWindow::SetSelectedNode(std::shared_ptr node) { m_selectedNode = node; } diff --git a/story-editor/src/properties_window.h b/story-editor/src/properties_window.h index 09d0ee3..ff9cc9d 100644 --- a/story-editor/src/properties_window.h +++ b/story-editor/src/properties_window.h @@ -3,7 +3,7 @@ #include "window_base.h" #include "gui.h" -#include "base_node.h" +#include "base_node_widget.h" class PropertiesWindow : public WindowBase { @@ -13,10 +13,10 @@ public: void Initialize(); virtual void Draw() override; - void SetSelectedNode(std::shared_ptr node); + void SetSelectedNode(std::shared_ptr node); private: - std::shared_ptr m_selectedNode; + std::shared_ptr m_selectedNode; };