Add function node with sub editor

This commit is contained in:
anthony@rabine.fr 2024-08-06 17:18:01 +02:00
parent cd26f407fc
commit f0ffb62867
21 changed files with 662 additions and 218 deletions

View file

@ -24,11 +24,13 @@ jobs:
mkdir build mkdir build
cd build cd build
cmake -DCMAKE_BUILD_TYPE=Release .. cmake -DCMAKE_BUILD_TYPE=Release ..
make
ls ls
- name: package_setup - name: package_setup
working-directory: ./story-editor/build working-directory: ./story-editor/build
run : | run : |
make package cpack
cpack -G DEB
ls ls
build_win32: build_win32:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -39,4 +41,4 @@ jobs:
- name: build - name: build
working-directory: ./story-editor working-directory: ./story-editor
run : | run : |
./build_win32.sh ./build_win32.sh

95
.vscode/settings.json vendored
View file

@ -6,11 +6,102 @@
"${workspaceFolder}/software" "${workspaceFolder}/software"
], ],
"files.associations": { "files.associations": {
"*.css": "tailwindcss",
"**/frontmatter.json": "jsonc", "**/frontmatter.json": "jsonc",
"**/.frontmatter/config/*.json": "jsonc", "**/.frontmatter/config/*.json": "jsonc",
"*.css": "tailwindcss",
"pico_i2s.pio.h": "c", "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"
} }
} }

View file

@ -8,12 +8,14 @@
#include "story_project.h" #include "story_project.h"
#include "json.hpp" #include "json.hpp"
#include "media_node.h" #include "media_node.h"
#include "function_node.h"
#include "sys_lib.h" #include "sys_lib.h"
StoryProject::StoryProject(ILogger &log) StoryProject::StoryProject(ILogger &log)
: m_log(log) : m_log(log)
{ {
registerNode<MediaNode>("media-node"); registerNode<MediaNode>("media-node");
registerNode<FunctionNode>("function-node");
} }
StoryProject::~StoryProject() StoryProject::~StoryProject()

View file

@ -49,8 +49,6 @@ struct StoryProject : public IStoryProject
{ {
public: public:
StoryProject(ILogger &log); StoryProject(ILogger &log);
~StoryProject(); ~StoryProject();
@ -142,6 +140,12 @@ public:
void AddConnection(std::shared_ptr<Connection> c); void AddConnection(std::shared_ptr<Connection> c);
void DeleteNode(const std::string &id); void DeleteNode(const std::string &id);
void DeleteLink(std::shared_ptr<Connection> c); void DeleteLink(std::shared_ptr<Connection> c);
std::vector<std::string> GetNodeTypes() const {
std::vector<std::string> l;
for(auto const& imap: m_registry) l.push_back(imap.first);
return l;
}
private: private:
ILogger &m_log; ILogger &m_log;

View file

@ -157,7 +157,7 @@ FetchContent_Declare(
set(SDL3IMAGE_INSTALL OFF) set(SDL3IMAGE_INSTALL OFF)
set(BUILD_SHARED_LIBS FALSE) set(BUILD_SHARED_LIBS TRUE)
FetchContent_MakeAvailable(sdl_image) FetchContent_MakeAvailable(sdl_image)
include_directories(${sdl_image_SOURCE_DIR}/include) include_directories(${sdl_image_SOURCE_DIR}/include)
@ -191,6 +191,8 @@ set(SRCS
src/node_engine/base_node.cpp src/node_engine/base_node.cpp
src/node_engine/media_node.h src/node_engine/media_node.h
src/node_engine/media_node.cpp 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.cpp
src/node_engine/connection.h src/node_engine/connection.h
@ -200,6 +202,8 @@ set(SRCS
src/node_editor/base_node_widget.cpp src/node_editor/base_node_widget.cpp
src/node_editor/node_editor_window.h src/node_editor/node_editor_window.h
src/node_editor/node_editor_window.cpp 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.cpp
src/resources_window.h src/resources_window.h
@ -340,6 +344,7 @@ target_link_directories(${STORY_EDITOR_PROJECT} PUBLIC ${sdl3_BINARY_DIR} ${curl
# certaines informations à CPACK # certaines informations à CPACK
set(SDL_BIN_DIR ${sdl3_BINARY_DIR}) set(SDL_BIN_DIR ${sdl3_BINARY_DIR})
set(SDL_IMAGE_BIN_DIR ${sdl_image_BINARY_DIR}) set(SDL_IMAGE_BIN_DIR ${sdl_image_BINARY_DIR})
set(SDL_MIXER_BIN_DIR ${sdl3_mixer_BINARY_DIR})
if(UNIX) if(UNIX)
target_link_libraries(${STORY_EDITOR_PROJECT} target_link_libraries(${STORY_EDITOR_PROJECT}
@ -357,6 +362,7 @@ elseif(WIN32)
OpenGL::GL OpenGL::GL
SDL3::SDL3 SDL3::SDL3
SDL3_image::SDL3_image SDL3_image::SDL3_image
SDL3_mixer::SDL3_mixer
libcurl_static libcurl_static
ws2_32.lib psapi.lib setupapi.lib cfgmgr32.lib advapi32.lib 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 # Personnaliser les options d'installation
set(CPACK_PACKAGE_NAME "Open-Story-Editor") 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_DESCRIPTION "Open Story Teller - Node based editor")
set(CPACK_PACKAGE_VENDOR "D8S") set(CPACK_PACKAGE_VENDOR "D8S")
set(CPACK_PACKAGE_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}") set(CPACK_PACKAGE_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}")
@ -384,6 +391,8 @@ install_files("." FILES "${CMAKE_SOURCE_DIR}/tools/imgui.ini")
if(WIN32) if(WIN32)
install_files("." FILES "${SDL_BIN_DIR}/SDL3.dll") 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/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/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") install_files("." FILES "/usr/lib/gcc/x86_64-w64-mingw32/10-posix/libgcc_s_seh-1.dll")
@ -393,9 +402,10 @@ if(WIN32)
endif() endif()
if(LINUX) 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() endif()
if (APPLE) if (APPLE)

View file

@ -36,6 +36,7 @@ public:
virtual void Log(const std::string &txt, bool critical = false) = 0; virtual void Log(const std::string &txt, bool critical = false) = 0;
virtual void PlaySoundFile(const std::string &fileName) = 0; virtual void PlaySoundFile(const std::string &fileName) = 0;
virtual std::string BuildFullAssetsPath(const std::string_view fileName) const = 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 // Resources management
virtual std::pair<FilterIterator, FilterIterator> Images() = 0; virtual std::pair<FilterIterator, FilterIterator> Images() = 0;

View file

@ -822,7 +822,7 @@ void MainWindow::CloseProject()
m_resources.Clear(); m_resources.Clear();
m_nodeEditorWindow.Clear(); m_nodeEditorWindow.Initialize();
m_emulatorWindow.ClearImage(); m_emulatorWindow.ClearImage();
m_consoleWindow.ClearLog(); m_consoleWindow.ClearLog();
m_codeEditorWindow.ClearErrors(); m_codeEditorWindow.ClearErrors();
@ -980,6 +980,11 @@ std::pair<FilterIterator, FilterIterator> MainWindow::Sounds()
return m_resources.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<Resource> res) void MainWindow::AddResource(std::shared_ptr<Resource> res)
{ {
m_resources.Add(res); m_resources.Add(res);

View file

@ -130,6 +130,7 @@ private:
virtual std::string BuildFullAssetsPath(const std::string_view fileName) const override; virtual std::string BuildFullAssetsPath(const std::string_view fileName) const override;
virtual std::pair<FilterIterator, FilterIterator> Images() override; virtual std::pair<FilterIterator, FilterIterator> Images() override;
virtual std::pair<FilterIterator, FilterIterator> Sounds() override; virtual std::pair<FilterIterator, FilterIterator> Sounds() override;
virtual void OpenFunction(const std::string &uuid, const std::string &name) override;
virtual void AddResource(std::shared_ptr<Resource> res) override; virtual void AddResource(std::shared_ptr<Resource> res) override;
virtual void ClearResources() override; virtual void ClearResources() override;

View file

@ -17,6 +17,7 @@
#define DR_MP3_IMPLEMENTATION #define DR_MP3_IMPLEMENTATION
#include "dr_mp3.h" #include "dr_mp3.h"
#define QOI_IMPLEMENTATION
#include "qoi.h" #include "qoi.h"

View file

@ -9,6 +9,7 @@ BaseNodeWidget::BaseNodeWidget(IStoryManager &manager, std::shared_ptr<BaseNode
: m_manager(manager) : m_manager(manager)
, m_base(base) , m_base(base)
{ {
// A node is specific to the Node Gfx library
m_node = std::make_unique<Node>(GetNextId(), ""); // ImGui internal ID m_node = std::make_unique<Node>(GetNextId(), ""); // ImGui internal ID
std::cout << " --> Created node widget: " << (int)m_node->ID.Get() << std::endl; 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() 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::Dummy(ImVec2(320 - textWidth * 2, 0)); // Hacky magic number to space out the output pin.
ImGui::SameLine(); ImGui::SameLine();
ed::BeginPin(output.ID, ed::PinKind::Output); 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(); ed::EndPin();
} }
} }

View file

@ -106,6 +106,7 @@ public:
virtual void Draw() = 0; virtual void Draw() = 0;
virtual void DrawProperties() = 0; virtual void DrawProperties() = 0;
void SetOutPinName(int pinIndex, const std::string &name);
void FrameStart(); void FrameStart();
void FrameEnd(); void FrameEnd();

View file

@ -0,0 +1,51 @@
#include <sstream>
#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<BaseNode> 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()
{
}

View file

@ -0,0 +1,29 @@
#pragma once
#include <vector>
#include <map>
#include <mutex>
#include <set>
#include "base_node_widget.h"
#include "i_story_manager.h"
#include "i_story_project.h"
#include "gui.h"
#include <imgui_node_editor.h>
#include "media_node.h"
class FunctionNodeWidget : public BaseNodeWidget
{
public:
FunctionNodeWidget(IStoryManager &manager, std::shared_ptr<BaseNode> node);
void Draw() override;
virtual void DrawProperties() override;
virtual void Initialize() override;
private:
IStoryManager &m_manager;
std::string m_functionName;
std::string m_functionUuid;
};

View file

@ -0,0 +1,222 @@
#pragma once
#include <map>
#include <set>
#include <list>
#include <utility>
#include <imgui_node_editor.h>
#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<EditorLink>();
model = std::make_shared<Connection>();
}
std::shared_ptr<EditorLink> ed_link;
std::shared_ptr<Connection> model;
};
struct NodeEditorPage {
ed::Config config;
ed::EditorContext* EditorContext;
std::list<std::shared_ptr<BaseNodeWidget>> m_nodes;
std::list<std::shared_ptr<LinkInfo>> 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<BaseNodeWidget> &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<BaseNodeWidget>& node) {
return node->GetInternalId() == nodeId.Get();
});
}
std::shared_ptr<BaseNodeWidget> GetSelectedNode() {
std::shared_ptr<BaseNodeWidget> 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<BaseNodeWidget> node) {
m_nodes.push_back(node);
}
void Clear() {
m_nodes.clear();
m_links.clear();
}
bool GetModel(ed::LinkId linkId, std::shared_ptr<Connection> &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>& 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;
};

View file

@ -10,6 +10,7 @@
#include "IconsFontAwesome5_c.h" #include "IconsFontAwesome5_c.h"
#include "media_node_widget.h" #include "media_node_widget.h"
#include "function_node_widget.h"
#include "gui.h" #include "gui.h"
#include "uuid.h" #include "uuid.h"
@ -27,71 +28,62 @@ NodeEditorWindow::NodeEditorWindow(IStoryManager &manager)
{ {
registerNode<MediaNodeWidget>("media-node"); registerNode<MediaNodeWidget>("media-node");
registerNode<FunctionNodeWidget>("function-node");
} }
NodeEditorWindow::~NodeEditorWindow() 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() void NodeEditorWindow::Initialize()
{ {
ed::Config config; m_pages.clear();
config.SettingsFile = nullptr; m_callStack.clear();
config.SaveSettings = nullptr;
config.LoadSettings = nullptr;
m_context = ed::CreateEditor(&config);
ed::SetCurrentEditor(m_context); m_currentPage = std::make_shared<NodeEditorPage>(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(); // On cherche la page correspondante dans la std::list
m_links.clear(); // Si elle n'existe pas, c'est une nouvelle fonction
m_story.reset(); auto it = std::find_if(m_pages.begin(), m_pages.end(), [uuid](std::shared_ptr<NodeEditorPage> p) { return p->Uuid() == uuid; });
std::shared_ptr<NodeEditorPage> page;
if (it == m_pages.end())
{
// New page
page = std::make_shared<NodeEditorPage>(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 NodeEditorWindow::GetInputPin(const std::string &modelNodeId, int pinIndex)
{ {
ed::PinId id = 0; return m_currentPage->GetInputPin(modelNodeId, pinIndex);
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 NodeEditorWindow::GetOutputPin(const std::string &modelNodeId, int pinIndex) ed::PinId NodeEditorWindow::GetOutputPin(const std::string &modelNodeId, int pinIndex)
{ {
ed::PinId id = 0; return m_currentPage->GetOutputPin(modelNodeId, pinIndex);
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;
} }
void NodeEditorWindow::Load(std::shared_ptr<StoryProject> story) void NodeEditorWindow::Load(std::shared_ptr<StoryProject> story)
@ -103,8 +95,7 @@ void NodeEditorWindow::Load(std::shared_ptr<StoryProject> story)
try { try {
BaseNodeWidget::InitId(); BaseNodeWidget::InitId();
m_nodes.clear(); Initialize();
m_links.clear();
auto [node_begin, node_end] = m_story->Nodes(); auto [node_begin, node_end] = m_story->Nodes();
@ -117,7 +108,7 @@ void NodeEditorWindow::Load(std::shared_ptr<StoryProject> story)
{ {
n->Initialize(); 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 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 else
{ {
@ -143,7 +134,7 @@ void NodeEditorWindow::Load(std::shared_ptr<StoryProject> 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<Connection> model, ed::PinId inId, ed::PinId outId) void NodeEditorWindow::CreateLink(std::shared_ptr<Connection> model, ed::PinId inId, ed::PinId outId)
{ {
auto conn = std::make_shared<LinkInfo>(); auto conn = std::make_shared<LinkInfo>();
@ -164,41 +161,16 @@ void NodeEditorWindow::CreateLink(std::shared_ptr<Connection> model, ed::PinId i
conn->ed_link->OutputId = outId; conn->ed_link->OutputId = outId;
// Since we accepted new link, lets add one to our list of links. // 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<Connection> c, ed::PinId pinId) bool NodeEditorWindow::FillConnection(std::shared_ptr<Connection> c, ed::PinId pinId)
{ {
bool success = false; bool success = false;
std::string nodeId; std::string nodeId;
int nodeIndex; int nodeIndex;
int ret = FindNodeAndPin(pinId, nodeIndex, nodeId); int ret = m_currentPage->FindNodeAndPin(pinId, nodeIndex, nodeId);
if (ret > 0) if (ret > 0)
{ {
if (ret == 1) if (ret == 1)
@ -216,53 +188,13 @@ bool NodeEditorWindow::FillConnection(std::shared_ptr<Connection> c, ed::PinId p
return success; return success;
} }
/*
std::shared_ptr<Connection> NodeEditorWindow::LinkToModel(ed::PinId InputId, ed::PinId OutputId)
{
auto c = std::make_shared<Connection>();
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<BaseNodeWidget> NodeEditorWindow::GetSelectedNode() std::shared_ptr<BaseNodeWidget> NodeEditorWindow::GetSelectedNode()
{ {
std::shared_ptr<BaseNodeWidget> selected; std::shared_ptr<BaseNodeWidget> selected;
ed::SetCurrentEditor(m_context); m_currentPage->Select();
if (ed::GetSelectedObjectCount() > 0) selected = m_currentPage->GetSelectedNode();
{
ed::NodeId nId;
int nodeCount = ed::GetSelectedNodes(&nId, 1);
if (nodeCount > 0)
{
for (auto & n : m_nodes)
{
if (n->GetInternalId() == nId.Get())
{
selected = n;
}
}
}
}
ed::SetCurrentEditor(nullptr); ed::SetCurrentEditor(nullptr);
return selected; return selected;
@ -271,24 +203,23 @@ std::shared_ptr<BaseNodeWidget> NodeEditorWindow::GetSelectedNode()
void NodeEditorWindow::Draw() void NodeEditorWindow::Draw()
{ {
if (!m_newPageUuid.empty())
{
LoadPage(m_newPageUuid, m_newPageName);
m_newPageUuid.clear();
}
if (WindowBase::BeginDraw()) if (WindowBase::BeginDraw())
{ {
m_currentPage->Select();
ed::SetCurrentEditor(m_context); ToolbarUI();
ed::Begin("My Editor", ImVec2(0.0, 0.0f));
ed::Begin(m_currentPage->Uuid().data(), ImVec2(0.0, 0.0f));
for (const auto & n : m_nodes) // Draw our nodes
{ m_currentPage->Draw();
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);
}
// Handle creation action, returns true if editor want to create new object (node or link) // Handle creation action, returns true if editor want to create new object (node or link)
if (ed::BeginCreate()) if (ed::BeginCreate())
@ -325,7 +256,7 @@ void NodeEditorWindow::Draw()
CreateLink(c, startId, endId); CreateLink(c, startId, endId);
// Draw new link. // 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()) if (ed::AcceptDeletedItem())
{ {
auto it = std::find_if(m_nodes.begin(), m_nodes.end(), [nodeId](std::shared_ptr<BaseNodeWidget> node) { return node->GetInternalId() == nodeId.Get(); }); std::shared_ptr<BaseNodeWidget> node;
if (it != m_nodes.end()) if (m_currentPage->GetNode(nodeId, node))
{ {
// First delete model, then current entry // First delete model, then current entry
m_manager.DeleteNode((*it)->Base()->GetId()); m_manager.DeleteNode(node->Base()->GetId());
m_nodes.erase(it); m_currentPage->DeleteNode(nodeId);
} }
} }
} }
// There may be many links marked for deletion, let's loop over them. // There may be many links marked for deletion, let's loop over them.
ed::LinkId deletedLinkId; ed::LinkId deletedLinkId;
while (ed::QueryDeletedLink(&deletedLinkId)) while (ed::QueryDeletedLink(&deletedLinkId))
@ -365,13 +295,11 @@ void NodeEditorWindow::Draw()
// If you agree that link can be deleted, accept deletion. // If you agree that link can be deleted, accept deletion.
if (ed::AcceptDeletedItem()) if (ed::AcceptDeletedItem())
{ {
std::shared_ptr<Connection> model;
auto it = std::find_if(m_links.begin(), m_links.end(), [deletedLinkId](std::shared_ptr<LinkInfo> inf) { return inf->ed_link->Id == deletedLinkId; }); if (m_currentPage->GetModel(deletedLinkId, model))
if (it != m_links.end())
{ {
// First delete model, then current entry m_manager.DeleteLink(model);
m_manager.DeleteLink((*it)->model); m_currentPage->EraseLink(deletedLinkId);
m_links.erase(it);
} }
} }
@ -393,18 +321,23 @@ void NodeEditorWindow::Draw()
if (ImGui::BeginPopup("Create New Node")) if (ImGui::BeginPopup("Create New Node"))
{ {
auto newNodePostion = openPopupPosition; auto newNodePostion = openPopupPosition;
Node* node = nullptr; std::shared_ptr<BaseNode> base;
if (ImGui::MenuItem("Media Node")) auto nodeTypes = m_story->GetNodeTypes();
for (auto &type : nodeTypes)
{ {
auto base = m_manager.CreateNode("media-node"); if (ImGui::MenuItem(type.c_str()))
if (base)
{ {
auto n = CreateNodeWidget(base->GetType(), m_manager, base); base = m_manager.CreateNode(type);
if (n) if (base)
{ {
n->Base()->SetPosition(newNodePostion.x, newNodePostion.y); auto n = CreateNodeWidget(type, m_manager, base);
n->Initialize(); if (n)
m_nodes.push_back(n); {
n->Base()->SetPosition(newNodePostion.x, newNodePostion.y);
n->Initialize();
m_currentPage->AddNode(n);
}
} }
} }
} }
@ -420,7 +353,7 @@ void NodeEditorWindow::Draw()
ed::Resume(); ed::Resume();
ed::End(); ed::End();
ed::SetCurrentEditor(nullptr); ed::SetCurrentEditor(nullptr);
@ -431,30 +364,49 @@ void NodeEditorWindow::Draw()
void NodeEditorWindow::ToolbarUI() void NodeEditorWindow::ToolbarUI()
{ {
ImGui::SetNextWindowPos(ImVec2(0, 0)); auto& io = ImGui::GetIO();
ImGui::SetNextWindowSize(ImVec2(42, Gui::GetWindowSize().h)); 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::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)) LoadPage(page->Uuid().data(), page->Name().data());
{ break;
// mEvent.ExitGame(); }
ImGui::SameLine();
ImGui::Text(">");
ImGui::SameLine();
} }
ImGui::End(); ImGui::End();
// ImGui::PopStyleVar();
// ImGui::PopStyleColor(4);
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
{
io.ConfigViewportsNoDecoration = false;
}
} }

View file

@ -2,7 +2,7 @@
#include <map> #include <map>
#include <set> #include <set>
#include <set> #include <utility>
#include <imgui_node_editor.h> #include <imgui_node_editor.h>
#include "base_node_widget.h" #include "base_node_widget.h"
@ -10,12 +10,10 @@
#include "i_story_manager.h" #include "i_story_manager.h"
#include "json.hpp" #include "json.hpp"
#include "story_project.h" #include "story_project.h"
#include "node_editor_page.h"
namespace ed = ax::NodeEditor; namespace ed = ax::NodeEditor;
# ifdef _MSC_VER # ifdef _MSC_VER
# define portable_strcpy strcpy_s # define portable_strcpy strcpy_s
# define portable_sprintf sprintf_s # define portable_sprintf sprintf_s
@ -26,35 +24,15 @@ namespace ed = ax::NodeEditor;
class NodeEditorWindow : public WindowBase class NodeEditorWindow : public WindowBase
{ {
public: 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<EditorLink>();
model = std::make_shared<Connection>();
}
std::shared_ptr<EditorLink> ed_link;
std::shared_ptr<Connection> model;
};
NodeEditorWindow(IStoryManager &manager); NodeEditorWindow(IStoryManager &manager);
~NodeEditorWindow(); ~NodeEditorWindow();
virtual void Draw() override; virtual void Draw() override;
void Initialize(); void Initialize();
void Clear();
void Load(std::shared_ptr<StoryProject> story); void Load(std::shared_ptr<StoryProject> story);
void SaveNodePositions(); void SaveNodePositions();
void OpenFunction(const std::string &uuid, const std::string &name);
std::shared_ptr<BaseNodeWidget> GetSelectedNode(); std::shared_ptr<BaseNodeWidget> GetSelectedNode();
@ -63,11 +41,16 @@ private:
bool m_loaded{false}; 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<std::shared_ptr<NodeEditorPage>> m_pages;
std::shared_ptr<NodeEditorPage> m_currentPage;
std::string m_newPageUuid;
std::string m_newPageName;
std::shared_ptr<StoryProject> m_story; std::shared_ptr<StoryProject> m_story;
std::list<std::shared_ptr<BaseNodeWidget>> m_nodes; std::list<std::shared_ptr<NodeEditorPage>> m_callStack;
std::list<std::shared_ptr<LinkInfo>> m_links; // List of live links. It is dynamic unless you want to create read-only view over nodes.
void ToolbarUI(); void ToolbarUI();
void BuildNode(Node* node) 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 GetInputPin(const std::string &modelNodeId, int pinIndex);
ed::PinId GetOutputPin(const std::string &modelNodeId, int pinIndex); ed::PinId GetOutputPin(const std::string &modelNodeId, int pinIndex);
void CreateLink(std::shared_ptr<Connection> model, ed::PinId inId, ed::PinId outId); void CreateLink(std::shared_ptr<Connection> model, ed::PinId inId, ed::PinId outId);
// std::shared_ptr<Connection> LinkToModel(ed::PinId InputId, ed::PinId OutputId);
int FindNodeAndPin(ed::PinId pinId, int &foundIndex, std::string &foundNodeId);
bool FillConnection(std::shared_ptr<Connection> c, ed::PinId pinId); bool FillConnection(std::shared_ptr<Connection> c, ed::PinId pinId);
}; };

View file

@ -2,9 +2,10 @@
#include "uuid.h" #include "uuid.h"
#include <iostream> #include <iostream>
BaseNode::BaseNode(const std::string &type) BaseNode::BaseNode(const std::string &type, const std::string &typeName)
{ {
m_type = type; m_type = type;
m_typeName = typeName;
m_uuid = Uuid().String(); m_uuid = Uuid().String();
nlohmann::json obj{}; nlohmann::json obj{};

View file

@ -17,7 +17,7 @@ public:
float y; float y;
}; };
BaseNode(const std::string &type); BaseNode(const std::string &type, const std::string &typeName);
virtual ~BaseNode(); virtual ~BaseNode();
static std::string GetEntryLabel(const std::string &id); static std::string GetEntryLabel(const std::string &id);
@ -32,11 +32,18 @@ public:
virtual float GetX() const; virtual float GetX() const;
virtual float GetY() const; virtual float GetY() const;
// Coded type, internal use
std::string GetType() const std::string GetType() const
{ {
return m_type; return m_type;
} }
// Human readable type
std::string GetTypeName() const
{
return m_typeName;
}
void SetId(const std::string &id) { m_uuid = id; } void SetId(const std::string &id) { m_uuid = id; }
std::string GetId() const { return m_uuid; } std::string GetId() const { return m_uuid; }
@ -54,6 +61,7 @@ public:
private: private:
std::string m_title{"Default title"}; std::string m_title{"Default title"};
std::string m_type; std::string m_type;
std::string m_typeName;
std::string m_uuid; std::string m_uuid;
NodePosition m_pos; NodePosition m_pos;

View file

@ -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<std::string>();
// m_sound = j["sound"].get<std::string>();
}
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;
}

View file

@ -0,0 +1,23 @@
#pragma once
#include <string>
#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:
};

View file

@ -11,7 +11,7 @@ static std::string ChoiceLabel(const std::string &id)
} }
MediaNode::MediaNode(const std::string &type) MediaNode::MediaNode(const std::string &type)
: BaseNode(type) : BaseNode(type, "Media Node")
{ {
nlohmann::json j{ {"image", ""}, {"sound", ""}}; nlohmann::json j{ {"image", ""}, {"sound", ""}};
SetInternalData(j); SetInternalData(j);