diff --git a/.github/workflows/story_editor.yml b/.github/workflows/story_editor.yml index 333a190..05022f9 100644 --- a/.github/workflows/story_editor.yml +++ b/.github/workflows/story_editor.yml @@ -24,11 +24,13 @@ jobs: mkdir build cd build cmake -DCMAKE_BUILD_TYPE=Release .. + make ls - name: package_setup working-directory: ./story-editor/build run : | - make package + cpack + cpack -G DEB ls build_win32: runs-on: ubuntu-latest @@ -39,4 +41,4 @@ jobs: - name: build working-directory: ./story-editor run : | - ./build_win32.sh \ No newline at end of file + ./build_win32.sh diff --git a/.vscode/settings.json b/.vscode/settings.json index 84dc12f..80c7c9f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,11 +6,102 @@ "${workspaceFolder}/software" ], "files.associations": { + "*.css": "tailwindcss", "**/frontmatter.json": "jsonc", "**/.frontmatter/config/*.json": "jsonc", - "*.css": "tailwindcss", "pico_i2s.pio.h": "c", - "pico_i2s.h": "c" + "pico_i2s.h": "c", + "cctype": "cpp", + "cmath": "cpp", + "cstddef": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "array": "cpp", + "atomic": "cpp", + "*.tcc": "cpp", + "chrono": "cpp", + "cstdint": "cpp", + "list": "cpp", + "map": "cpp", + "set": "cpp", + "string": "cpp", + "unordered_map": "cpp", + "vector": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "optional": "cpp", + "random": "cpp", + "ratio": "cpp", + "regex": "cpp", + "string_view": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "fstream": "cpp", + "future": "cpp", + "iomanip": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "mutex": "cpp", + "ostream": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "thread": "cpp", + "variant": "cpp", + "bitset": "cpp", + "*.ipp": "cpp", + "deque": "cpp", + "forward_list": "cpp", + "unordered_set": "cpp", + "initializer_list": "cpp", + "ranges": "cpp", + "span": "cpp", + "valarray": "cpp", + "format": "cpp", + "samd21.h": "c", + "ost_hal.h": "c", + "debug.h": "c", + "numbers": "cpp", + "qtconcurrentdepends": "cpp", + "qtcoredepends": "cpp", + "qtguidepends": "cpp", + "qtnetworkdepends": "cpp", + "qtopengldepends": "cpp", + "qtqmldepends": "cpp", + "qtwidgetsdepends": "cpp", + "complex": "cpp", + "system_error": "cpp", + "typeindex": "cpp", + "typeinfo": "cpp", + "compare": "cpp", + "any": "cpp", + "filesystem": "cpp", + "stdbool.h": "c", + "bit": "cpp", + "charconv": "cpp", + "cinttypes": "cpp", + "clocale": "cpp", + "codecvt": "cpp", + "concepts": "cpp", + "condition_variable": "cpp", + "cstdarg": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "new": "cpp", + "semaphore": "cpp", + "shared_mutex": "cpp", + "stop_token": "cpp", + "streambuf": "cpp", + "cfenv": "cpp" } } \ No newline at end of file diff --git a/shared/story_project.cpp b/shared/story_project.cpp index 3a00e4f..894cbd5 100644 --- a/shared/story_project.cpp +++ b/shared/story_project.cpp @@ -8,12 +8,14 @@ #include "story_project.h" #include "json.hpp" #include "media_node.h" +#include "function_node.h" #include "sys_lib.h" StoryProject::StoryProject(ILogger &log) : m_log(log) { registerNode("media-node"); + registerNode("function-node"); } StoryProject::~StoryProject() diff --git a/shared/story_project.h b/shared/story_project.h index 5f514bf..5921606 100644 --- a/shared/story_project.h +++ b/shared/story_project.h @@ -49,8 +49,6 @@ struct StoryProject : public IStoryProject { public: - - StoryProject(ILogger &log); ~StoryProject(); @@ -142,6 +140,12 @@ public: void AddConnection(std::shared_ptr c); void DeleteNode(const std::string &id); void DeleteLink(std::shared_ptr c); + + std::vector GetNodeTypes() const { + std::vector l; + for(auto const& imap: m_registry) l.push_back(imap.first); + return l; + } private: ILogger &m_log; diff --git a/story-editor/CMakeLists.txt b/story-editor/CMakeLists.txt index 6e760bf..f541dd1 100644 --- a/story-editor/CMakeLists.txt +++ b/story-editor/CMakeLists.txt @@ -157,7 +157,7 @@ FetchContent_Declare( set(SDL3IMAGE_INSTALL OFF) -set(BUILD_SHARED_LIBS FALSE) +set(BUILD_SHARED_LIBS TRUE) FetchContent_MakeAvailable(sdl_image) include_directories(${sdl_image_SOURCE_DIR}/include) @@ -191,6 +191,8 @@ set(SRCS src/node_engine/base_node.cpp src/node_engine/media_node.h src/node_engine/media_node.cpp + src/node_engine/function_node.h + src/node_engine/function_node.cpp src/node_engine/connection.cpp src/node_engine/connection.h @@ -200,6 +202,8 @@ set(SRCS src/node_editor/base_node_widget.cpp src/node_editor/node_editor_window.h src/node_editor/node_editor_window.cpp + src/node_editor/function_node_widget.h + src/node_editor/function_node_widget.cpp src/resources_window.cpp src/resources_window.h @@ -340,6 +344,7 @@ target_link_directories(${STORY_EDITOR_PROJECT} PUBLIC ${sdl3_BINARY_DIR} ${curl # certaines informations à CPACK set(SDL_BIN_DIR ${sdl3_BINARY_DIR}) set(SDL_IMAGE_BIN_DIR ${sdl_image_BINARY_DIR}) +set(SDL_MIXER_BIN_DIR ${sdl3_mixer_BINARY_DIR}) if(UNIX) target_link_libraries(${STORY_EDITOR_PROJECT} @@ -357,6 +362,7 @@ elseif(WIN32) OpenGL::GL SDL3::SDL3 SDL3_image::SDL3_image + SDL3_mixer::SDL3_mixer libcurl_static ws2_32.lib psapi.lib setupapi.lib cfgmgr32.lib advapi32.lib ) @@ -369,6 +375,7 @@ install(TARGETS ${STORY_EDITOR_PROJECT} RUNTIME DESTINATION ".") # Personnaliser les options d'installation set(CPACK_PACKAGE_NAME "Open-Story-Editor") +set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Anthony Rabine") set(CPACK_PACKAGE_DESCRIPTION "Open Story Teller - Node based editor") set(CPACK_PACKAGE_VENDOR "D8S") set(CPACK_PACKAGE_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}") @@ -384,6 +391,8 @@ install_files("." FILES "${CMAKE_SOURCE_DIR}/tools/imgui.ini") if(WIN32) install_files("." FILES "${SDL_BIN_DIR}/SDL3.dll") + install_files("." FILES "${SDL_IMAGE_BIN_DIR}/SDL3_image.dll") + install_files("." FILES "${SDL_MIXER_BIN_DIR}/SDL3_mixer.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") @@ -393,9 +402,10 @@ if(WIN32) endif() if(LINUX) - install_files("." FILES "${SDL_BIN_DIR}/SDL3.so") - install_files("." FILES "${SDL_IMAGE_BIN_DIR}/SDL3_image.so") - + + install_files("." FILES "${SDL_BIN_DIR}/libSDL3.so") + install_files("." FILES "${SDL_IMAGE_BIN_DIR}/libSDL3_image.so") + install_files("." FILES "${SDL_MIXER_BIN_DIR}/libSDL3_mixer.so") endif() if (APPLE) diff --git a/story-editor/src/i_story_manager.h b/story-editor/src/i_story_manager.h index 22a517a..d381f8c 100644 --- a/story-editor/src/i_story_manager.h +++ b/story-editor/src/i_story_manager.h @@ -36,6 +36,7 @@ public: virtual void Log(const std::string &txt, bool critical = false) = 0; virtual void PlaySoundFile(const std::string &fileName) = 0; virtual std::string BuildFullAssetsPath(const std::string_view fileName) const = 0; + virtual void OpenFunction(const std::string &uuid, const std::string &name) = 0; // Resources management virtual std::pair Images() = 0; diff --git a/story-editor/src/main_window.cpp b/story-editor/src/main_window.cpp index a26980b..a8d3695 100644 --- a/story-editor/src/main_window.cpp +++ b/story-editor/src/main_window.cpp @@ -822,7 +822,7 @@ void MainWindow::CloseProject() m_resources.Clear(); - m_nodeEditorWindow.Clear(); + m_nodeEditorWindow.Initialize(); m_emulatorWindow.ClearImage(); m_consoleWindow.ClearLog(); m_codeEditorWindow.ClearErrors(); @@ -980,6 +980,11 @@ std::pair MainWindow::Sounds() return m_resources.Sounds(); } +void MainWindow::OpenFunction(const std::string &uuid, const std::string &name) +{ + m_nodeEditorWindow.OpenFunction(uuid, name); +} + void MainWindow::AddResource(std::shared_ptr res) { m_resources.Add(res); diff --git a/story-editor/src/main_window.h b/story-editor/src/main_window.h index 0a2d8e0..fbe43c9 100644 --- a/story-editor/src/main_window.h +++ b/story-editor/src/main_window.h @@ -130,6 +130,7 @@ private: virtual std::string BuildFullAssetsPath(const std::string_view fileName) const override; virtual std::pair Images() override; virtual std::pair Sounds() override; + virtual void OpenFunction(const std::string &uuid, const std::string &name) override; virtual void AddResource(std::shared_ptr res) override; virtual void ClearResources() override; diff --git a/story-editor/src/media_converter.cpp b/story-editor/src/media_converter.cpp index e0763eb..cf055a1 100644 --- a/story-editor/src/media_converter.cpp +++ b/story-editor/src/media_converter.cpp @@ -17,6 +17,7 @@ #define DR_MP3_IMPLEMENTATION #include "dr_mp3.h" +#define QOI_IMPLEMENTATION #include "qoi.h" diff --git a/story-editor/src/node_editor/base_node_widget.cpp b/story-editor/src/node_editor/base_node_widget.cpp index 427fd95..d8a692e 100644 --- a/story-editor/src/node_editor/base_node_widget.cpp +++ b/story-editor/src/node_editor/base_node_widget.cpp @@ -9,6 +9,7 @@ BaseNodeWidget::BaseNodeWidget(IStoryManager &manager, std::shared_ptr(GetNextId(), ""); // ImGui internal ID std::cout << " --> Created node widget: " << (int)m_node->ID.Get() << std::endl; } @@ -58,6 +59,13 @@ void BaseNodeWidget::Initialize() } +void BaseNodeWidget::SetOutPinName(int pinIndex, const std::string &name) +{ + if (pinIndex < m_node->Outputs.size()) + { + m_node->Outputs[pinIndex].Name = name; + } +} void BaseNodeWidget::FrameStart() { @@ -107,7 +115,14 @@ void BaseNodeWidget::DrawPins() ImGui::Dummy(ImVec2(320 - textWidth * 2, 0)); // Hacky magic number to space out the output pin. ImGui::SameLine(); ed::BeginPin(output.ID, ed::PinKind::Output); - ImGui::Text( "#%d " ICON_MDI_OCTAGON_OUTLINE, i++); + if (output.Name.empty()) + { + ImGui::Text( "#%d " ICON_MDI_OCTAGON_OUTLINE, i++); + } + else + { + ImGui::Text( "%s" ICON_MDI_OCTAGON_OUTLINE, output.Name.c_str() ); + } ed::EndPin(); } } diff --git a/story-editor/src/node_editor/base_node_widget.h b/story-editor/src/node_editor/base_node_widget.h index 55f8769..7ec9bc8 100644 --- a/story-editor/src/node_editor/base_node_widget.h +++ b/story-editor/src/node_editor/base_node_widget.h @@ -106,6 +106,7 @@ public: virtual void Draw() = 0; virtual void DrawProperties() = 0; + void SetOutPinName(int pinIndex, const std::string &name); void FrameStart(); void FrameEnd(); diff --git a/story-editor/src/node_editor/function_node_widget.cpp b/story-editor/src/node_editor/function_node_widget.cpp new file mode 100644 index 0000000..06839bb --- /dev/null +++ b/story-editor/src/node_editor/function_node_widget.cpp @@ -0,0 +1,51 @@ + +#include +#include "function_node_widget.h" + +namespace ed = ax::NodeEditor; +#include "IconsMaterialDesignIcons.h" +#include "story_project.h" +#include "uuid.h" + +FunctionNodeWidget::FunctionNodeWidget(IStoryManager &manager, std::shared_ptr node) + : BaseNodeWidget(manager, node) + , m_manager(manager) +{ + // Create defaut one input and one output + AddInput(); + AddOutputs(2); + SetOutPinName(0, "Success"); + SetOutPinName(1, "Failure"); + m_functionUuid = Uuid().String(); +} + +void FunctionNodeWidget::Draw() +{ + BaseNodeWidget::FrameStart(); + + ImGui::TextUnformatted(m_functionName.c_str()); + if (ImGui::Button("> Open function")) + { + m_manager.OpenFunction(m_functionUuid, m_functionName); + } + + DrawPins(); + + BaseNodeWidget::FrameEnd(); + +} + +void FunctionNodeWidget::Initialize() +{ + BaseNodeWidget::Initialize(); + m_functionName = "Function"; +} + +void FunctionNodeWidget::DrawProperties() +{ + + + +} + + diff --git a/story-editor/src/node_editor/function_node_widget.h b/story-editor/src/node_editor/function_node_widget.h new file mode 100644 index 0000000..cfc37aa --- /dev/null +++ b/story-editor/src/node_editor/function_node_widget.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include +#include + +#include "base_node_widget.h" +#include "i_story_manager.h" +#include "i_story_project.h" +#include "gui.h" +#include +#include "media_node.h" + +class FunctionNodeWidget : public BaseNodeWidget +{ +public: + FunctionNodeWidget(IStoryManager &manager, std::shared_ptr node); + + void Draw() override; + + virtual void DrawProperties() override; + virtual void Initialize() override; + +private: + IStoryManager &m_manager; + std::string m_functionName; + std::string m_functionUuid; +}; diff --git a/story-editor/src/node_editor/node_editor_page.h b/story-editor/src/node_editor/node_editor_page.h new file mode 100644 index 0000000..ec5e304 --- /dev/null +++ b/story-editor/src/node_editor/node_editor_page.h @@ -0,0 +1,222 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include "base_node_widget.h" + +namespace ed = ax::NodeEditor; + +struct EditorLink { + ed::LinkId Id; + ed::PinId InputId; + ed::PinId OutputId; +}; + +// Stuff from ImGuiNodeEditor, each element has a unique ID within one editor +struct LinkInfo +{ + + LinkInfo() + { + ed_link = std::make_shared(); + model = std::make_shared(); + } + + std::shared_ptr ed_link; + std::shared_ptr model; +}; + + +struct NodeEditorPage { + ed::Config config; + ed::EditorContext* EditorContext; + 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. + + NodeEditorPage(const std::string &uuid, const std::string &name) + : m_uuid(uuid) + , m_name(name) + { + config.SettingsFile = nullptr; + config.SaveSettings = nullptr; + config.LoadSettings = nullptr; + EditorContext = ed::CreateEditor(&config); + } + + std::string_view Name() const { + return m_name; + } + + std::string_view Uuid() const { + return m_uuid; + } + + ~NodeEditorPage() { + ed::DestroyEditor(EditorContext); + Clear(); + } + + void Select() + { + ed::SetCurrentEditor(EditorContext); + } + + void Draw() { + for (const auto & n : m_nodes) + { + ImGui::PushID(n->GetInternalId()); + n->Draw(); + ImGui::PopID(); + } + + for (const auto& linkInfo : m_links) + { + ed::Link(linkInfo->ed_link->Id, linkInfo->ed_link->OutputId, linkInfo->ed_link->InputId); + } + } + + bool GetNode(const ed::NodeId &nodeId, std::shared_ptr &node) { + for (const auto & n : m_nodes) + { + if (n->GetInternalId() == nodeId.Get()) + { + node = n; + return true; + } + } + return false; + } + + void DeleteNode(const ed::NodeId &nodeId) { + m_nodes.remove_if([nodeId](const std::shared_ptr& node) { + return node->GetInternalId() == nodeId.Get(); + }); + } + + std::shared_ptr GetSelectedNode() { + + std::shared_ptr selected; + + if (ed::GetSelectedObjectCount() > 0) + { + ed::NodeId nId; + int nodeCount = ed::GetSelectedNodes(&nId, 1); + + if (nodeCount > 0) + { + for (auto & n : m_nodes) + { + if (n->GetInternalId() == nId.Get()) + { + selected = n; + } + } + } + } + return selected; + } + + void AddNode(std::shared_ptr node) { + m_nodes.push_back(node); + } + + void Clear() { + m_nodes.clear(); + m_links.clear(); + } + + bool GetModel(ed::LinkId linkId, std::shared_ptr &model) { + for (const auto& linkInfo : m_links) + { + if (linkInfo->ed_link->Id == linkId) + { + model = linkInfo->model; + return true; + } + } + return false; + } + + void EraseLink(ed::LinkId linkId) { + m_links.remove_if([linkId](const std::shared_ptr& linkInfo) { + return linkInfo->ed_link->Id == linkId; + }); + } + + // retourne 1 si c'est une sortie, 2 une entrée, 0 pas trouvé + int FindNodeAndPin(ed::PinId pinId, int &foundIndex, std::string &foundNodeId) + { + int success = 0; + for (const auto & n : m_nodes) + { + // std::cout << "---> Node: " << n->Base()->GetId() << std::endl; + + if (n->HasOnputPinId(pinId, foundIndex)) + { + foundNodeId = n->Base()->GetId(); + success = 1; + break; + } + + if (n->HasInputPinId(pinId, foundIndex)) + { + foundNodeId = n->Base()->GetId(); + success = 2; + break; + } + } + + return success; + } + + ed::PinId GetInputPin(const std::string &modelNodeId, int pinIndex) + { + ed::PinId id = 0; + + for (auto & n : m_nodes) + { + if (n->Base()->GetId() == modelNodeId) + { + id = n->GetInputPinAt(pinIndex); + break; + } + } + + if (id.Get() == 0) + { + std::cout << "Invalid Id: " << modelNodeId << " input pin: " << pinIndex <<" not found" << std::endl; + } + + return id; + } + + ed::PinId GetOutputPin(const std::string &modelNodeId, int pinIndex) +{ + ed::PinId id = 0; + + for (auto & n : m_nodes) + { + if (n->Base()->GetId() == modelNodeId) + { + id = n->GetOutputPinAt(pinIndex); + break; + } + } + + if (id.Get() == 0) + { + std::cout << "Invalid Id: " << modelNodeId << " output pin: " << pinIndex <<" not found" << std::endl; + } + + return id; + } + +private: + std::string m_uuid; + std::string m_name; + +}; diff --git a/story-editor/src/node_editor/node_editor_window.cpp b/story-editor/src/node_editor/node_editor_window.cpp index f36e75b..2cbaf18 100644 --- a/story-editor/src/node_editor/node_editor_window.cpp +++ b/story-editor/src/node_editor/node_editor_window.cpp @@ -10,6 +10,7 @@ #include "IconsFontAwesome5_c.h" #include "media_node_widget.h" +#include "function_node_widget.h" #include "gui.h" #include "uuid.h" @@ -27,71 +28,62 @@ NodeEditorWindow::NodeEditorWindow(IStoryManager &manager) { registerNode("media-node"); + registerNode("function-node"); } NodeEditorWindow::~NodeEditorWindow() { - ed::DestroyEditor(m_context); + m_pages.clear(); + m_story.reset(); } +static const std::string gMainUuid = "490745ab-df4d-476d-ae27-027e94b8ee0a"; + void NodeEditorWindow::Initialize() { - ed::Config config; - config.SettingsFile = nullptr; - config.SaveSettings = nullptr; - config.LoadSettings = nullptr; - m_context = ed::CreateEditor(&config); + m_pages.clear(); + m_callStack.clear(); - ed::SetCurrentEditor(m_context); + m_currentPage = std::make_shared(gMainUuid, "Main"); + m_pages.emplace_back(m_currentPage); + + m_currentPage->Select(); } -void NodeEditorWindow::Clear() +void NodeEditorWindow::LoadPage(const std::string &uuid, const std::string &name) { - m_nodes.clear(); - m_links.clear(); - m_story.reset(); + // On cherche la page correspondante dans la std::list + // Si elle n'existe pas, c'est une nouvelle fonction + auto it = std::find_if(m_pages.begin(), m_pages.end(), [uuid](std::shared_ptr p) { return p->Uuid() == uuid; }); + + std::shared_ptr page; + + if (it == m_pages.end()) + { + // New page + page = std::make_shared(uuid, name); + } + else + { + page = *it; + } + + if (m_currentPage->Uuid() != uuid) + { + m_callStack.push_back(m_currentPage); // save where we are + m_currentPage = page; + m_currentPage->Select(); + } } ed::PinId NodeEditorWindow::GetInputPin(const std::string &modelNodeId, int pinIndex) { - ed::PinId id = 0; - - for (auto & n : m_nodes) - { - if (n->Base()->GetId() == modelNodeId) - { - id = n->GetInputPinAt(pinIndex); - break; - } - } - - if (id.Get() == 0) - { - std::cout << "Invalid Id: " << modelNodeId << " input pin: " << pinIndex <<" not found" << std::endl; - } - - return id; + return m_currentPage->GetInputPin(modelNodeId, pinIndex); } ed::PinId NodeEditorWindow::GetOutputPin(const std::string &modelNodeId, int pinIndex) { - ed::PinId id = 0; - - for (auto & n : m_nodes) - { - if (n->Base()->GetId() == modelNodeId) - { - id = n->GetOutputPinAt(pinIndex); - break; - } - } - - if (id.Get() == 0) - { - std::cout << "Invalid Id: " << modelNodeId << " output pin: " << pinIndex <<" not found" << std::endl; - } - - return id; + return m_currentPage->GetOutputPin(modelNodeId, pinIndex); } void NodeEditorWindow::Load(std::shared_ptr story) @@ -103,8 +95,7 @@ void NodeEditorWindow::Load(std::shared_ptr story) try { BaseNodeWidget::InitId(); - m_nodes.clear(); - m_links.clear(); + Initialize(); auto [node_begin, node_end] = m_story->Nodes(); @@ -117,7 +108,7 @@ void NodeEditorWindow::Load(std::shared_ptr story) { n->Initialize(); n->SetOutputs(m_story->OutputsCount((*it)->GetId())); // il faut que les noeuds aient une bonne taille de outputs avant de créer les liens - m_nodes.push_back(n); + m_currentPage->AddNode(n); } else { @@ -143,7 +134,7 @@ void NodeEditorWindow::Load(std::shared_ptr story) } } - std::cout << "Loaded " << m_nodes.size() << " nodes, " << m_links.size() << " links" << std::endl; + std::cout << "Loaded " << m_currentPage->m_nodes.size() << " nodes, " << m_currentPage->m_links.size() << " links" << std::endl; } @@ -152,6 +143,12 @@ void NodeEditorWindow::SaveNodePositions() } +void NodeEditorWindow::OpenFunction(const std::string &uuid, const std::string &name) +{ + m_newPageUuid = uuid; + m_newPageName = name; +} + void NodeEditorWindow::CreateLink(std::shared_ptr model, ed::PinId inId, ed::PinId outId) { auto conn = std::make_shared(); @@ -164,41 +161,16 @@ void NodeEditorWindow::CreateLink(std::shared_ptr model, ed::PinId i conn->ed_link->OutputId = outId; // Since we accepted new link, lets add one to our list of links. - m_links.push_back(conn); + m_currentPage->m_links.push_back(conn); } -// retourne 1 si c'est une sortie, 2 une entrée, 0 pas trouvé -int NodeEditorWindow::FindNodeAndPin(ed::PinId pinId, int &foundIndex, std::string &foundNodeId) -{ - int success = 0; - for (const auto & n : m_nodes) - { - // std::cout << "---> Node: " << n->Base()->GetId() << std::endl; - - if (n->HasOnputPinId(pinId, foundIndex)) - { - foundNodeId = n->Base()->GetId(); - success = 1; - break; - } - - if (n->HasInputPinId(pinId, foundIndex)) - { - foundNodeId = n->Base()->GetId(); - success = 2; - break; - } - } - - return success; -} bool NodeEditorWindow::FillConnection(std::shared_ptr c, ed::PinId pinId) { bool success = false; std::string nodeId; int nodeIndex; - int ret = FindNodeAndPin(pinId, nodeIndex, nodeId); + int ret = m_currentPage->FindNodeAndPin(pinId, nodeIndex, nodeId); if (ret > 0) { if (ret == 1) @@ -216,53 +188,13 @@ bool NodeEditorWindow::FillConnection(std::shared_ptr c, ed::PinId p return success; } -/* -std::shared_ptr NodeEditorWindow::LinkToModel(ed::PinId InputId, ed::PinId OutputId) -{ - auto c = std::make_shared(); - int foundIndex = -1; - for (const auto & n : m_nodes) - { - // std::cout << "---> Node: " << n->Base()->GetId() << std::endl; - - if (n->HasOnputPinId(OutputId, foundIndex)) - { - c->outNodeId = n->Base()->GetId(); - c->outPortIndex = foundIndex; - } - - if (n->HasInputPinId(InputId, foundIndex)) - { - c->inNodeId = n->Base()->GetId(); - c->inPortIndex = foundIndex; - } - } - - return c; -}*/ - std::shared_ptr NodeEditorWindow::GetSelectedNode() { std::shared_ptr selected; - ed::SetCurrentEditor(m_context); - if (ed::GetSelectedObjectCount() > 0) - { - ed::NodeId nId; - int nodeCount = ed::GetSelectedNodes(&nId, 1); - - if (nodeCount > 0) - { - for (auto & n : m_nodes) - { - if (n->GetInternalId() == nId.Get()) - { - selected = n; - } - } - } - } + m_currentPage->Select(); + selected = m_currentPage->GetSelectedNode(); ed::SetCurrentEditor(nullptr); return selected; @@ -271,24 +203,23 @@ std::shared_ptr NodeEditorWindow::GetSelectedNode() void NodeEditorWindow::Draw() { + if (!m_newPageUuid.empty()) + { + LoadPage(m_newPageUuid, m_newPageName); + m_newPageUuid.clear(); + } + if (WindowBase::BeginDraw()) { + m_currentPage->Select(); - ed::SetCurrentEditor(m_context); - ed::Begin("My Editor", ImVec2(0.0, 0.0f)); + ToolbarUI(); + + ed::Begin(m_currentPage->Uuid().data(), ImVec2(0.0, 0.0f)); - for (const auto & n : m_nodes) - { - ImGui::PushID(n->GetInternalId()); - n->Draw(); - ImGui::PopID(); - } - - for (const auto& linkInfo : m_links) - { - ed::Link(linkInfo->ed_link->Id, linkInfo->ed_link->OutputId, linkInfo->ed_link->InputId); - } + // Draw our nodes + m_currentPage->Draw(); // Handle creation action, returns true if editor want to create new object (node or link) if (ed::BeginCreate()) @@ -325,7 +256,7 @@ void NodeEditorWindow::Draw() CreateLink(c, startId, endId); // Draw new link. - ed::Link(m_links.back()->ed_link->Id, startId, endId); + ed::Link(m_currentPage->m_links.back()->ed_link->Id, startId, endId); } } } @@ -347,17 +278,16 @@ void NodeEditorWindow::Draw() { if (ed::AcceptDeletedItem()) { - auto it = std::find_if(m_nodes.begin(), m_nodes.end(), [nodeId](std::shared_ptr node) { return node->GetInternalId() == nodeId.Get(); }); - if (it != m_nodes.end()) + std::shared_ptr node; + if (m_currentPage->GetNode(nodeId, node)) { // First delete model, then current entry - m_manager.DeleteNode((*it)->Base()->GetId()); - m_nodes.erase(it); + m_manager.DeleteNode(node->Base()->GetId()); + m_currentPage->DeleteNode(nodeId); } } } - // There may be many links marked for deletion, let's loop over them. ed::LinkId deletedLinkId; while (ed::QueryDeletedLink(&deletedLinkId)) @@ -365,13 +295,11 @@ void NodeEditorWindow::Draw() // If you agree that link can be deleted, accept deletion. if (ed::AcceptDeletedItem()) { - - auto it = std::find_if(m_links.begin(), m_links.end(), [deletedLinkId](std::shared_ptr inf) { return inf->ed_link->Id == deletedLinkId; }); - if (it != m_links.end()) + std::shared_ptr model; + if (m_currentPage->GetModel(deletedLinkId, model)) { - // First delete model, then current entry - m_manager.DeleteLink((*it)->model); - m_links.erase(it); + m_manager.DeleteLink(model); + m_currentPage->EraseLink(deletedLinkId); } } @@ -393,18 +321,23 @@ void NodeEditorWindow::Draw() if (ImGui::BeginPopup("Create New Node")) { auto newNodePostion = openPopupPosition; - Node* node = nullptr; - if (ImGui::MenuItem("Media Node")) + std::shared_ptr base; + auto nodeTypes = m_story->GetNodeTypes(); + + for (auto &type : nodeTypes) { - auto base = m_manager.CreateNode("media-node"); - if (base) + if (ImGui::MenuItem(type.c_str())) { - auto n = CreateNodeWidget(base->GetType(), m_manager, base); - if (n) + base = m_manager.CreateNode(type); + if (base) { - n->Base()->SetPosition(newNodePostion.x, newNodePostion.y); - n->Initialize(); - m_nodes.push_back(n); + auto n = CreateNodeWidget(type, m_manager, base); + if (n) + { + n->Base()->SetPosition(newNodePostion.x, newNodePostion.y); + n->Initialize(); + m_currentPage->AddNode(n); + } } } } @@ -420,7 +353,7 @@ void NodeEditorWindow::Draw() ed::Resume(); - + ed::End(); ed::SetCurrentEditor(nullptr); @@ -431,30 +364,49 @@ void NodeEditorWindow::Draw() void NodeEditorWindow::ToolbarUI() { - ImGui::SetNextWindowPos(ImVec2(0, 0)); - ImGui::SetNextWindowSize(ImVec2(42, Gui::GetWindowSize().h)); + auto& io = ImGui::GetIO(); + ImVec2 window_pos = ImGui::GetWindowPos(); + ImVec2 window_size = ImGui::GetWindowSize(); + ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoDocking; + // if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + // { + // const ImGuiViewport* viewport = ImGui::GetWindowViewport(); + // window_flags |= ImGuiWindowFlags_NoDocking; + // io.ConfigViewportsNoDecoration = true; + // ImGui::SetNextWindowViewport(viewport->ID); + // } + ImGui::SetNextWindowBgAlpha(0.35f); // Transparent background + // ImGui::PushStyleColor(ImGuiCol_Button, m_StyleColors[BluePrintStyleColor_ToolButton]); + // ImGui::PushStyleColor(ImGuiCol_ButtonHovered, m_StyleColors[BluePrintStyleColor_ToolButtonHovered]); + // ImGui::PushStyleColor(ImGuiCol_ButtonActive, m_StyleColors[BluePrintStyleColor_ToolButtonActive]); + // ImGui::PushStyleColor(ImGuiCol_TexGlyphShadow, ImVec4(0.1, 0.1, 0.1, 0.8)); + // ImGui::PushStyleVar(ImGuiStyleVar_TexGlyphShadowOffset, ImVec2(2.0, 2.0)); - ImGuiWindowFlags window_flags = 0 - | ImGuiWindowFlags_NoTitleBar - | ImGuiWindowFlags_NoResize - | ImGuiWindowFlags_NoMove - | ImGuiWindowFlags_NoScrollbar - | ImGuiWindowFlags_NoSavedSettings - ; - ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0); ImGui::Begin("TOOLBAR", NULL, window_flags); - ImGui::PopStyleVar(); + // ImGui::PopStyleVar(); - if (ImGui::Button(ICON_FA_COG)) + // Draw call stack, each function is a button + for (auto page : m_callStack) { - ImGui::OpenPopup("Options"); - } + if (ImGui::Button(page->Name().data())) + { + // Erase all pages after this iterator + auto it = std::find(m_callStack.begin(), m_callStack.end(), page); + m_callStack.erase(it, m_callStack.end()); - if (ImGui::Button(ICON_FA_SIGN_OUT_ALT)) - { - // mEvent.ExitGame(); + LoadPage(page->Uuid().data(), page->Name().data()); + break; + } + ImGui::SameLine(); + ImGui::Text(">"); + ImGui::SameLine(); } - ImGui::End(); + // ImGui::PopStyleVar(); + // ImGui::PopStyleColor(4); + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + { + io.ConfigViewportsNoDecoration = false; + } } diff --git a/story-editor/src/node_editor/node_editor_window.h b/story-editor/src/node_editor/node_editor_window.h index 365f048..cbe7ad0 100644 --- a/story-editor/src/node_editor/node_editor_window.h +++ b/story-editor/src/node_editor/node_editor_window.h @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include "base_node_widget.h" @@ -10,12 +10,10 @@ #include "i_story_manager.h" #include "json.hpp" #include "story_project.h" - +#include "node_editor_page.h" namespace ed = ax::NodeEditor; - - # ifdef _MSC_VER # define portable_strcpy strcpy_s # define portable_sprintf sprintf_s @@ -26,35 +24,15 @@ namespace ed = ax::NodeEditor; class NodeEditorWindow : public WindowBase { -public: - struct EditorLink { - ed::LinkId Id; - ed::PinId InputId; - ed::PinId OutputId; - }; - - // Stuff from ImGuiNodeEditor, each element has a unique ID within one editor - struct LinkInfo - { - - LinkInfo() - { - ed_link = std::make_shared(); - model = std::make_shared(); - } - - std::shared_ptr ed_link; - std::shared_ptr model; - }; - +public: NodeEditorWindow(IStoryManager &manager); ~NodeEditorWindow(); virtual void Draw() override; void Initialize(); - void Clear(); void Load(std::shared_ptr story); void SaveNodePositions(); + void OpenFunction(const std::string &uuid, const std::string &name); std::shared_ptr GetSelectedNode(); @@ -63,11 +41,16 @@ private: bool m_loaded{false}; - ed::EditorContext* m_context = nullptr; - + // "main" is the entry point editor context. You always need to create one. + // Then each function can have its own editor context, for example if you want to create multiple graphs. + // the key is main, or the UUID of the function + std::list> m_pages; + std::shared_ptr m_currentPage; + std::string m_newPageUuid; + std::string m_newPageName; std::shared_ptr m_story; - 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. + std::list> m_callStack; + void ToolbarUI(); void BuildNode(Node* node) @@ -113,11 +96,10 @@ private: } } + void LoadPage(const std::string &uuid, const std::string &name); ed::PinId GetInputPin(const std::string &modelNodeId, int pinIndex); ed::PinId GetOutputPin(const std::string &modelNodeId, int pinIndex); void CreateLink(std::shared_ptr model, ed::PinId inId, ed::PinId outId); - // std::shared_ptr LinkToModel(ed::PinId InputId, ed::PinId OutputId); - int FindNodeAndPin(ed::PinId pinId, int &foundIndex, std::string &foundNodeId); bool FillConnection(std::shared_ptr c, ed::PinId pinId); }; diff --git a/story-editor/src/node_engine/base_node.cpp b/story-editor/src/node_engine/base_node.cpp index a60f1ad..11fe0fb 100644 --- a/story-editor/src/node_engine/base_node.cpp +++ b/story-editor/src/node_engine/base_node.cpp @@ -2,9 +2,10 @@ #include "uuid.h" #include -BaseNode::BaseNode(const std::string &type) +BaseNode::BaseNode(const std::string &type, const std::string &typeName) { m_type = type; + m_typeName = typeName; m_uuid = Uuid().String(); nlohmann::json obj{}; diff --git a/story-editor/src/node_engine/base_node.h b/story-editor/src/node_engine/base_node.h index 4b42c8d..72cc4ea 100644 --- a/story-editor/src/node_engine/base_node.h +++ b/story-editor/src/node_engine/base_node.h @@ -17,7 +17,7 @@ public: float y; }; - BaseNode(const std::string &type); + BaseNode(const std::string &type, const std::string &typeName); virtual ~BaseNode(); static std::string GetEntryLabel(const std::string &id); @@ -32,11 +32,18 @@ public: virtual float GetX() const; virtual float GetY() const; + // Coded type, internal use std::string GetType() const { return m_type; } + // Human readable type + std::string GetTypeName() const + { + return m_typeName; + } + void SetId(const std::string &id) { m_uuid = id; } std::string GetId() const { return m_uuid; } @@ -54,6 +61,7 @@ public: private: std::string m_title{"Default title"}; std::string m_type; + std::string m_typeName; std::string m_uuid; NodePosition m_pos; diff --git a/story-editor/src/node_engine/function_node.cpp b/story-editor/src/node_engine/function_node.cpp new file mode 100644 index 0000000..3ae7065 --- /dev/null +++ b/story-editor/src/node_engine/function_node.cpp @@ -0,0 +1,43 @@ +#include "function_node.h" +#include "story_project.h" +#include "connection.h" +#include "sys_lib.h" + + +FunctionNode::FunctionNode(const std::string &type) + : BaseNode(type, "Function Node") +{ + nlohmann::json j{ {"function", ""} }; + SetInternalData(j); +} + +void FunctionNode::StoreInternalData() +{ + nlohmann::json j; + // j["image"] = m_image; + // j["sound"] = m_sound; + + SetInternalData(j); +} + +void FunctionNode::Initialize() +{ + nlohmann::json j = GetInternalData(); + // m_image = j["image"].get(); + // m_sound = j["sound"].get(); +} + +std::string FunctionNode::Build(IStoryProject &story, int nb_out_conns) +{ + return std::string(); +} + +std::string FunctionNode::GenerateConstants(IStoryProject &story, int nb_out_conns) +{ + std::string s; + + + + return s; +} + diff --git a/story-editor/src/node_engine/function_node.h b/story-editor/src/node_engine/function_node.h new file mode 100644 index 0000000..64cb32a --- /dev/null +++ b/story-editor/src/node_engine/function_node.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include "i_story_manager.h" +#include "base_node.h" +#include "i_script_node.h" +#include "i_story_project.h" + +class FunctionNode : public BaseNode +{ +public: + FunctionNode(const std::string &type); + + virtual void Initialize() override; + virtual std::string Build(IStoryProject &story, int nb_out_conns) override; + virtual std::string GenerateConstants(IStoryProject &story, int nb_out_conns) override; + + void StoreInternalData(); + +private: + +}; + diff --git a/story-editor/src/node_engine/media_node.cpp b/story-editor/src/node_engine/media_node.cpp index 81d729f..905139f 100644 --- a/story-editor/src/node_engine/media_node.cpp +++ b/story-editor/src/node_engine/media_node.cpp @@ -11,7 +11,7 @@ static std::string ChoiceLabel(const std::string &id) } MediaNode::MediaNode(const std::string &type) - : BaseNode(type) + : BaseNode(type, "Media Node") { nlohmann::json j{ {"image", ""}, {"sound", ""}}; SetInternalData(j);