more separation between node UI logic and editor, working Studio format support

This commit is contained in:
Anthony 2024-04-24 14:15:57 +02:00
parent 6c4ffbbd62
commit a6dbc90b78
19 changed files with 536 additions and 340 deletions

View file

@ -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 "$<$<CONFIG:DEBUG>: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)

View file

@ -46,8 +46,8 @@ public:
// Node interaction
virtual void Build() = 0;
virtual std::list<std::shared_ptr<Connection>> GetNodeConnections(unsigned long nodeId) = 0;
virtual std::string GetNodeEntryLabel(unsigned long nodeId) = 0;
virtual std::list<std::shared_ptr<Connection>> 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;

View file

@ -1,8 +1,3 @@
#include "pack_archive.h"
#include "ni_parser.h"
#include "json.hpp"
#include "serializers.h"
#include <iostream>
#include <algorithm>
#include <fstream>
@ -10,6 +5,15 @@
#include <iomanip>
#include <filesystem>
#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<std::string>());
// 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<Resource>();
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<std::string, std::string> stageActionLink;
nlohmann::json jnodes = nlohmann::json::array();
for (const auto & n : j["stageNodes"])
{
nlohmann::json node;
auto node_uuid = n["uuid"].get<std::string>();
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<std::string>() : "";
auto audio = n["audio"];
internalData["sound"] = audio.is_string() ? audio.get<std::string>() : "";
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<std::string>(); // 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"

View file

@ -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();

View file

@ -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<std::string>());
// 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<Resource>();
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<std::string>();
node["type"] = "media-node";
auto type = n["type"].get<std::string>();
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<std::string>() : "";
auto audio = n["audio"];
internalData["sound"] = audio.is_string() ? audio.get<std::string>() : "";
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<std::shared_ptr<Connection>> MainWindow::GetNodeConnections(unsigned long nodeId)
std::list<std::shared_ptr<Connection>> MainWindow::GetNodeConnections(const std::string &nodeId)
{
return m_nodeEditorWindow.GetNodeConnections(nodeId);
}

View file

@ -135,8 +135,8 @@ private:
virtual std::pair<FilterIterator, FilterIterator> Resources() override;
virtual void DeleteResource(FilterIterator &it) override;
virtual void Build() override;
virtual std::list<std::shared_ptr<Connection>> GetNodeConnections(unsigned long nodeId) override;
virtual std::string GetNodeEntryLabel(unsigned long nodeId) override;
virtual std::list<std::shared_ptr<Connection>> 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;

View file

@ -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 <stdio.h>
#include <stdlib.h>
// 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;
}
*/

View file

@ -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<Node>(GetNextId(), title.c_str()); // ImGui internal ID
m_node = std::make_unique<Node>(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;

View file

@ -2,12 +2,12 @@
#include <string>
#include <memory>
#include "json.hpp"
#include <random>
#include <string>
#include "json.hpp"
#include "i_story_manager.h"
#include "base_node.h"
#include <imgui_node_editor.h>
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<Node> 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;
};

View file

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

View file

@ -5,35 +5,36 @@
#include <mutex>
#include <set>
#include "base_node.h"
#include "base_node_widget.h"
#include "i_story_manager.h"
#include "gui.h"
#include <imgui_node_editor.h>
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();
};

View file

@ -11,6 +11,7 @@
#include "media_node.h"
#include "gui.h"
#include "uuid.h"
#include <stdexcept> // 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<int>();
nlohmann::json internalDataJson = nodeJson["internal-data"];
std::string type = nodeJson["type"].get<std::string>();
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<int>());
n->SetPosition(posJson["x"].get<double>(), posJson["y"].get<double>());
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<std::string>();
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<std::string, int> outputCounts;
for (auto& connection : connectionJsonArray)
{
Connection model = connection.get<Connection>();
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<std::shared_ptr<Connection>> NodeEditorWindow::GetNodeConnections(unsigned long nodeId)
std::list<std::shared_ptr<Connection>> NodeEditorWindow::GetNodeConnections(const std::string &nodeId)
{
std::list<std::shared_ptr<Connection>> 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<std::shared_ptr<Connection>> 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<BaseNode> NodeEditorWindow::GetSelectedNode()
std::shared_ptr<BaseNodeWidget> NodeEditorWindow::GetSelectedNode()
{
std::shared_ptr<BaseNode> selected;
std::shared_ptr<BaseNodeWidget> 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();
}

View file

@ -5,7 +5,7 @@
#include <set>
#include <imgui_node_editor.h>
#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<std::shared_ptr<Connection> > GetNodeConnections(unsigned long nodeId);
std::string GetNodeEntryLabel(unsigned long nodeId);
std::list<std::shared_ptr<Connection> > GetNodeConnections(const std::string &nodeId);
std::string GetNodeEntryLabel(const std::string &nodeId);
std::shared_ptr<BaseNode> GetSelectedNode();
std::shared_ptr<BaseNodeWidget> GetSelectedNode();
private:
IStoryManager &m_story;
@ -68,12 +68,10 @@ private:
bool m_loaded{false};
// key: Id
std::list<std::shared_ptr<BaseNode>> m_nodes;
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.
void ToolbarUI();
std::set<int> m_ids;
void BuildNode(Node* node)
{
for (auto& input : node->Inputs)
@ -91,12 +89,12 @@ private:
template<class NodeType>
struct Factory {
static std::shared_ptr<BaseNode> create_func(const std::string &title, IStoryManager &proj) {
return std::make_shared<NodeType>(title, proj);
static std::shared_ptr<BaseNodeWidget> create_func(const std::string &type, IStoryManager &proj) {
return std::make_shared<NodeType>(type, proj);
}
};
typedef std::shared_ptr<BaseNode> (*GenericCreator)(const std::string &title, IStoryManager &proj);
typedef std::shared_ptr<BaseNodeWidget> (*GenericCreator)(const std::string &type, IStoryManager &proj);
typedef std::map<std::string, GenericCreator> Registry;
Registry m_registry;
@ -105,20 +103,23 @@ private:
m_registry.insert(typename Registry::value_type(key, Factory<Derived>::create_func));
}
std::shared_ptr<BaseNode> createNode(const std::string& key, const std::string &title, IStoryManager &proj) {
std::shared_ptr<BaseNodeWidget> 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);
};

View file

@ -0,0 +1,70 @@
#include "base_node.h"
#include <iostream>
BaseNode::BaseNode(const std::string &type)
{
m_type = type;
}
void BaseNode::FromJson(const nlohmann::json &j)
{
try
{
m_uuid = j["uuid"].get<std::string>();
m_internal_data = j["internal-data"];
m_type = j["type"].get<std::string>();
m_title = j.value("title", "Default node");
nlohmann::json posJson = j["position"];
SetPosition(posJson["x"].get<double>(), posJson["y"].get<double>());
}
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;
}

View file

@ -0,0 +1,55 @@
#pragma once
#include <string>
#include <memory>
#include "json.hpp"
#include <random>
#include <string>
#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;
};

View file

@ -2,16 +2,17 @@
void to_json(nlohmann::json &j, const Connection &p) {
j = nlohmann::json{
{"outNodeId", static_cast<int64_t>(p.outNodeId)},
{"outNodeId", p.outNodeId },
{"outPortIndex", static_cast<int64_t>(p.outPortIndex)},
{"inNodeId", static_cast<int64_t>(p.inNodeId)},
{"inNodeId", p.inNodeId},
{"inPortIndex", static_cast<int64_t>(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<std::string>();
p.inNodeId = j["inNodeId"].get<std::string>();
p.outPortIndex = j["outPortIndex"].get<int>();
p.inPortIndex = j["inPortIndex"].get<int>();
}

View file

@ -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){

View file

@ -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<BaseNode> node)
void PropertiesWindow::SetSelectedNode(std::shared_ptr<BaseNodeWidget> node)
{
m_selectedNode = node;
}

View file

@ -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<BaseNode> node);
void SetSelectedNode(std::shared_ptr<BaseNodeWidget> node);
private:
std::shared_ptr<BaseNode> m_selectedNode;
std::shared_ptr<BaseNodeWidget> m_selectedNode;
};