mirror of
https://github.com/arabine/open-story-teller.git
synced 2025-12-06 17:09:06 +01:00
Big overall architecture change: separation between display and story model
This commit is contained in:
parent
4fc34d5521
commit
73aefa7100
28 changed files with 904 additions and 703 deletions
16
shared/i_story_project.h
Normal file
16
shared/i_story_project.h
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include "connection.h"
|
||||
|
||||
class IStoryProject
|
||||
{
|
||||
public:
|
||||
virtual ~IStoryProject() {};
|
||||
virtual std::list<std::shared_ptr<Connection>> GetNodeConnections(const std::string &nodeId) = 0;
|
||||
virtual int OutputsCount(const std::string &nodeId) = 0;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
|
@ -132,6 +132,13 @@ void LibraryManager::CopyToDevice(const std::string &outputDir)
|
|||
{
|
||||
std::thread myThread([&]() {
|
||||
myThread.detach();
|
||||
|
||||
std::cout << "Starting to copy elements" << std::endl;
|
||||
|
||||
for (auto p : *this)
|
||||
{
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ private:
|
|||
class IResource
|
||||
{
|
||||
public:
|
||||
virtual ~IResource();
|
||||
virtual ~IResource() {}
|
||||
|
||||
};
|
||||
|
||||
|
|
@ -6,10 +6,11 @@
|
|||
#include <regex>
|
||||
|
||||
#include "json.hpp"
|
||||
#include "media_node.h"
|
||||
|
||||
StoryProject::StoryProject()
|
||||
{
|
||||
|
||||
registerNode<MediaNode>("media-node");
|
||||
}
|
||||
|
||||
StoryProject::~StoryProject()
|
||||
|
|
@ -27,6 +28,13 @@ void StoryProject::SetPaths(const std::string &uuid, const std::string &library_
|
|||
std::cout << "Working dir is: " << m_working_dir << std::endl;
|
||||
}
|
||||
|
||||
void StoryProject::CopyToDevice(const std::string &outputDir)
|
||||
{
|
||||
ResourceManager manager;
|
||||
|
||||
Load(manager);
|
||||
|
||||
}
|
||||
|
||||
void StoryProject::New(const std::string &uuid, const std::string &library_path)
|
||||
{
|
||||
|
|
@ -70,8 +78,217 @@ bool StoryProject::ParseStoryInformation(nlohmann::json &j)
|
|||
return success;
|
||||
}
|
||||
|
||||
void StoryProject::ModelToJson(nlohmann::json &model)
|
||||
{
|
||||
nlohmann::json nodes = nlohmann::json::array();
|
||||
for (const auto & n : m_nodes)
|
||||
{
|
||||
nodes.push_back(n->ToJson());
|
||||
}
|
||||
|
||||
bool StoryProject::Load(nlohmann::json &model, ResourceManager &manager)
|
||||
model["nodes"] = nodes;
|
||||
|
||||
// Save links
|
||||
nlohmann::json connections = nlohmann::json::array();
|
||||
for (const auto& cnx : m_links)
|
||||
{
|
||||
nlohmann::json c(*cnx);
|
||||
connections.push_back(c);
|
||||
// Connection cnx = LinkToModel(linkInfo->ed_link->InputId, linkInfo->ed_link->OutputId);
|
||||
}
|
||||
|
||||
model["connections"] = connections;
|
||||
}
|
||||
|
||||
|
||||
std::shared_ptr<BaseNode> StoryProject::CreateNode(const std::string &type)
|
||||
{
|
||||
|
||||
typename Registry::const_iterator i = m_registry.find(type);
|
||||
if (i == m_registry.end()) {
|
||||
throw std::invalid_argument(std::string(__PRETTY_FUNCTION__) +
|
||||
": key not registered");
|
||||
}
|
||||
else
|
||||
{
|
||||
return i->second(type);
|
||||
}
|
||||
}
|
||||
|
||||
void StoryProject::AddConnection(std::shared_ptr<Connection> c)
|
||||
{
|
||||
m_links.push_back(c);
|
||||
}
|
||||
|
||||
void StoryProject::DeleteNode(const std::string &id)
|
||||
{
|
||||
auto it = std::find_if(m_nodes.begin(),
|
||||
m_nodes.end(),
|
||||
[&id](std::shared_ptr<BaseNode> const &n) { return n->GetId() == id; });
|
||||
|
||||
if ( it != m_nodes.end() )
|
||||
{
|
||||
it->reset();
|
||||
m_nodes.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void StoryProject::DeleteLink(std::shared_ptr<Connection> c)
|
||||
{
|
||||
auto it = std::find_if(m_links.begin(),
|
||||
m_links.end(),
|
||||
[&c](std::shared_ptr<Connection> const &cnx) {
|
||||
return *cnx == *c;
|
||||
});
|
||||
|
||||
if ( it != m_links.end() )
|
||||
{
|
||||
it->reset();
|
||||
m_links.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
bool StoryProject::ModelFromJson(const nlohmann::json &model)
|
||||
{
|
||||
bool success = false;
|
||||
try {
|
||||
|
||||
nlohmann::json nodesJsonArray = model["nodes"];
|
||||
|
||||
m_nodes.clear();
|
||||
m_links.clear();
|
||||
|
||||
for (auto& element : nodesJsonArray) {
|
||||
|
||||
std::string type = element["type"].get<std::string>();
|
||||
|
||||
auto n = CreateNode(type);
|
||||
if (n)
|
||||
{
|
||||
n->FromJson(element);
|
||||
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;
|
||||
|
||||
// Ici on reste flexible sur les connexions, cela permet de créer éventuellement des
|
||||
// projets sans fils (bon, l'élément devrait quand même exister dans le JSON)
|
||||
if (model.contains("connections"))
|
||||
{
|
||||
nlohmann::json connectionJsonArray = model["connections"];
|
||||
|
||||
// key: node UUID, value: output counts
|
||||
std::map<std::string, int> outputCounts;
|
||||
|
||||
for (auto& connection : connectionJsonArray)
|
||||
{
|
||||
m_links.push_back(std::make_shared<Connection>(connection.get<Connection>()));
|
||||
}
|
||||
}
|
||||
success = true;
|
||||
}
|
||||
catch(std::exception &e)
|
||||
{
|
||||
std::cout << "(NodeEditorWindow::Load) " << e.what() << std::endl;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
int StoryProject::OutputsCount(const std::string &nodeId)
|
||||
{
|
||||
int count = 0;
|
||||
for (const auto & l : m_links)
|
||||
{
|
||||
if (l->outNodeId == nodeId)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
std::list<std::shared_ptr<Connection>> StoryProject::GetNodeConnections(const std::string &nodeId)
|
||||
{
|
||||
std::list<std::shared_ptr<Connection>> c;
|
||||
|
||||
for (const auto & l : m_links)
|
||||
{
|
||||
if (l->outNodeId == nodeId)
|
||||
{
|
||||
c.push_back(l);
|
||||
}
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
std::string StoryProject::FindFirstNode() const
|
||||
{
|
||||
std::string id;
|
||||
|
||||
// First node is the one without connection on its input port
|
||||
|
||||
for (const auto & n : m_nodes)
|
||||
{
|
||||
bool foundConnection = false;
|
||||
|
||||
for (const auto& l : m_links)
|
||||
{
|
||||
if (l->inNodeId == n->GetId())
|
||||
{
|
||||
foundConnection = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundConnection)
|
||||
{
|
||||
id = n->GetId();
|
||||
std::cout << "First node is: " + id << std::endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
bool StoryProject::Build(std::string &codeStr)
|
||||
{
|
||||
std::stringstream code;
|
||||
std::stringstream chip32;
|
||||
|
||||
std::string firstNode = FindFirstNode();
|
||||
|
||||
if (firstNode == "")
|
||||
{
|
||||
std::cout << "First node not found, there must be only one node with a free input." << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
code << "\tjump " << BaseNode::GetEntryLabel(firstNode) << "\r\n";
|
||||
|
||||
// First generate all constants
|
||||
for (const auto & n : m_nodes)
|
||||
{
|
||||
code << n->GenerateConstants(*this, OutputsCount(n->GetId())) << "\n";
|
||||
}
|
||||
|
||||
for (const auto & n : m_nodes)
|
||||
{
|
||||
code << n->Build(*this, OutputsCount(n->GetId())) << "\n";
|
||||
}
|
||||
|
||||
codeStr = code.str();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StoryProject::Load(ResourceManager &manager)
|
||||
{
|
||||
try {
|
||||
std::ifstream f(m_project_file_path);
|
||||
|
|
@ -98,7 +315,7 @@ bool StoryProject::Load(nlohmann::json &model, ResourceManager &manager)
|
|||
|
||||
if (j.contains("nodegraph"))
|
||||
{
|
||||
model = j["nodegraph"];
|
||||
ModelFromJson(j["nodegraph"]);
|
||||
m_initialized = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -173,7 +390,7 @@ bool StoryProject::Load(nlohmann::json &model, ResourceManager &manager)
|
|||
return m_initialized;
|
||||
}
|
||||
|
||||
void StoryProject::Save(const nlohmann::json &model, ResourceManager &manager)
|
||||
void StoryProject::Save(ResourceManager &manager)
|
||||
{
|
||||
nlohmann::json j;
|
||||
j["project"] = { {"name", m_name}, {"uuid", m_uuid}, { "title_image", m_titleImage }, { "title_sound", m_titleSound } };
|
||||
|
|
@ -194,6 +411,8 @@ void StoryProject::Save(const nlohmann::json &model, ResourceManager &manager)
|
|||
j["resources"] = resourcesData;
|
||||
}
|
||||
|
||||
nlohmann::json model;
|
||||
ModelToJson(model);
|
||||
j["nodegraph"] = model;
|
||||
|
||||
std::ofstream o(m_project_file_path);
|
||||
|
|
@ -240,6 +459,8 @@ void StoryProject::Clear()
|
|||
m_working_dir = "";
|
||||
m_project_file_path = "";
|
||||
m_initialized = false;
|
||||
m_nodes.clear();
|
||||
m_links.clear();
|
||||
}
|
||||
|
||||
void StoryProject::EraseString(std::string &theString, const std::string &toErase)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@
|
|||
|
||||
#include "json.hpp"
|
||||
#include "resource_manager.h"
|
||||
|
||||
#include "connection.h"
|
||||
#include "base_node.h"
|
||||
#include "i_story_project.h"
|
||||
|
||||
// FIXME : Structure très Lunii style, à utiliser pour la conversion peut-être ...
|
||||
struct StoryNode
|
||||
|
|
@ -42,7 +44,7 @@ struct StoryNode
|
|||
};
|
||||
|
||||
|
||||
struct StoryProject
|
||||
struct StoryProject : public IStoryProject
|
||||
{
|
||||
|
||||
public:
|
||||
|
|
@ -51,6 +53,10 @@ public:
|
|||
|
||||
StoryProject();
|
||||
~StoryProject();
|
||||
|
||||
bool *Selected() {
|
||||
return &m_selected;
|
||||
}
|
||||
/*
|
||||
std::vector<StoryNode> m_nodes;
|
||||
|
||||
|
|
@ -60,14 +66,28 @@ public:
|
|||
StoryNode *m_tree;
|
||||
*/
|
||||
void New(const std::string &uuid, const std::string &library_path);
|
||||
bool Load(nlohmann::json &model, ResourceManager &manager);
|
||||
void Save(const nlohmann::json &model, ResourceManager &manager);
|
||||
bool Build(std::string &codeStr);
|
||||
bool Load(ResourceManager &manager);
|
||||
void Save(ResourceManager &manager);
|
||||
void SaveBinary(const std::vector<uint8_t> &m_program);
|
||||
void SetPaths(const std::string &uuid, const std::string &library_path);
|
||||
void CopyToDevice(const std::string &outputDir);
|
||||
|
||||
void ModelToJson(nlohmann::json &model);
|
||||
bool ModelFromJson(const nlohmann::json &model);
|
||||
|
||||
void CreateTree();
|
||||
void Clear();
|
||||
|
||||
|
||||
std::pair<std::list<std::shared_ptr<BaseNode>>::iterator, std::list<std::shared_ptr<BaseNode>>::iterator> Nodes() {
|
||||
return std::make_pair(m_nodes.begin(), m_nodes.end());
|
||||
}
|
||||
|
||||
std::pair<std::list<std::shared_ptr<Connection>>::iterator, std::list<std::shared_ptr<Connection>>::iterator> Links() {
|
||||
return std::make_pair(m_links.begin(), m_links.end());
|
||||
}
|
||||
|
||||
void Select(bool selected) { m_selected = selected; }
|
||||
bool IsSelected() const { return m_selected; }
|
||||
|
||||
|
|
@ -104,11 +124,21 @@ public:
|
|||
// Initialize with an existing project
|
||||
const bool IsInitialized() const { return m_initialized; }
|
||||
|
||||
|
||||
static void EraseString(std::string &theString, const std::string &toErase);
|
||||
static std::string ToUpper(const std::string &input);
|
||||
|
||||
bool ParseStoryInformation(nlohmann::json &j);
|
||||
|
||||
// From IStoryProject
|
||||
virtual std::list<std::shared_ptr<Connection>> GetNodeConnections(const std::string &nodeId) override;
|
||||
std::string FindFirstNode() const;
|
||||
virtual int OutputsCount(const std::string &nodeId) override;
|
||||
|
||||
std::shared_ptr<BaseNode> CreateNode(const std::string& type);
|
||||
void AddConnection(std::shared_ptr<Connection> c);
|
||||
void DeleteNode(const std::string &id);
|
||||
void DeleteLink(std::shared_ptr<Connection> c);
|
||||
|
||||
private:
|
||||
// Project properties and location
|
||||
std::string m_name; /// human readable name
|
||||
|
|
@ -121,6 +151,10 @@ private:
|
|||
|
||||
std::filesystem::path m_assetsPath;
|
||||
|
||||
// Model in memory
|
||||
std::list<std::shared_ptr<Connection>> m_links;
|
||||
std::list<std::shared_ptr<BaseNode>> m_nodes;
|
||||
|
||||
bool m_initialized{false};
|
||||
|
||||
std::filesystem::path m_working_dir; /// Temporary folder based on the uuid, where the archive is unzipped
|
||||
|
|
@ -131,6 +165,28 @@ private:
|
|||
|
||||
ImageFormat m_imageFormat{IMG_FORMAT_BMP_4BITS};
|
||||
SoundFormat m_soundFormat{SND_FORMAT_WAV};
|
||||
|
||||
|
||||
template<class NodeType>
|
||||
struct Factory {
|
||||
static std::shared_ptr<BaseNode> create_func(const std::string &type) {
|
||||
return std::make_shared<NodeType>(type);
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<BaseNode> (*GenericCreator)(const std::string &type);
|
||||
typedef std::map<std::string, GenericCreator> Registry;
|
||||
Registry m_registry;
|
||||
|
||||
template<class Derived>
|
||||
void registerNode(const std::string& key) {
|
||||
m_registry.insert(typename Registry::value_type(key, Factory<Derived>::create_func));
|
||||
}
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif // STORY_PROJECT_H
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -171,12 +171,13 @@ set(SRCS
|
|||
|
||||
src/node_engine/base_node.h
|
||||
src/node_engine/base_node.cpp
|
||||
src/node_engine/media_node.h
|
||||
src/node_engine/media_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/media_node_widget.h
|
||||
src/node_editor/media_node_widget.cpp
|
||||
src/node_editor/base_node_widget.h
|
||||
src/node_editor/base_node_widget.cpp
|
||||
src/node_editor/node_editor_window.h
|
||||
|
|
|
|||
|
|
@ -109,6 +109,6 @@ void EmulatorWindow::ClearImage()
|
|||
|
||||
void EmulatorWindow::SetImage(const std::string &image)
|
||||
{
|
||||
m_image.name = image;
|
||||
m_imageFileName = image;
|
||||
m_image.Load(m_story.BuildFullAssetsPath(image));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,5 +18,6 @@ public:
|
|||
private:
|
||||
IStoryManager &m_story;
|
||||
Gui::Image m_image;
|
||||
std::string m_imageFileName;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -18,8 +18,6 @@ public:
|
|||
int w;
|
||||
int h;
|
||||
|
||||
std::string name;
|
||||
|
||||
bool Valid() const {
|
||||
return (w && h);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include "resource.h"
|
||||
#include "connection.h"
|
||||
#include "base_node.h"
|
||||
|
||||
template <typename T>
|
||||
struct Callback;
|
||||
|
|
@ -46,8 +47,11 @@ public:
|
|||
|
||||
// Node interaction
|
||||
virtual void Build(bool compileonly) = 0;
|
||||
virtual std::shared_ptr<BaseNode> CreateNode(const std::string &type) = 0;
|
||||
virtual void DeleteNode(const std::string &id) = 0;
|
||||
virtual void DeleteLink(std::shared_ptr<Connection> c) = 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 Stop() = 0;
|
||||
|
|
|
|||
|
|
@ -429,7 +429,6 @@ bool PackArchive::ImportStudioFormat(const std::string &fileName, const std::str
|
|||
nlohmann::json j = nlohmann::json::parse(f);
|
||||
StoryProject proj;
|
||||
ResourceManager res;
|
||||
nlohmann::json model;
|
||||
|
||||
if (j.contains("title"))
|
||||
{
|
||||
|
|
@ -458,15 +457,16 @@ bool PackArchive::ImportStudioFormat(const std::string &fileName, const std::str
|
|||
// 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 = proj.CreateNode("media-node");
|
||||
|
||||
if (node)
|
||||
{
|
||||
auto node_uuid = n["uuid"].get<std::string>();
|
||||
node["uuid"] = node_uuid;
|
||||
node["type"] = "media-node";
|
||||
node["position"] = n["position"];
|
||||
node->SetId(node_uuid);
|
||||
node->SetPosition(n["position"]["x"].get<float>(), n["position"]["y"].get<float>());
|
||||
|
||||
nlohmann::json internalData;
|
||||
auto img = n["image"];
|
||||
|
|
@ -474,42 +474,36 @@ bool PackArchive::ImportStudioFormat(const std::string &fileName, const std::str
|
|||
auto audio = n["audio"];
|
||||
internalData["sound"] = audio.is_string() ? audio.get<std::string>() : "";
|
||||
|
||||
node["internal-data"] = internalData;
|
||||
node->SetInternalData(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;
|
||||
auto c = std::make_shared<Connection>();
|
||||
|
||||
c["outNodeId"] = stageActionLink[action_node_uuid];
|
||||
c["outPortIndex"] = i;
|
||||
c["inNodeId"] = m; // On prend le stage node;
|
||||
c["inPortIndex"] = 0;
|
||||
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);
|
||||
proj.AddConnection(c);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -517,11 +511,8 @@ bool PackArchive::ImportStudioFormat(const std::string &fileName, const std::str
|
|||
std::cout << "ActionNode UUID not found" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
model["connections"] = connections;
|
||||
|
||||
// Save on disk
|
||||
proj.Save(model, res);
|
||||
proj.Save(res);
|
||||
}
|
||||
}
|
||||
catch(std::exception &e)
|
||||
|
|
|
|||
|
|
@ -368,16 +368,6 @@ void LibraryWindow::Draw()
|
|||
WindowBase::BeginDraw();
|
||||
ImGui::SetWindowSize(ImVec2(626, 744), ImGuiCond_FirstUseEver);
|
||||
|
||||
if (ImGui::Button( ICON_MDI_FOLDER " Select directory"))
|
||||
{
|
||||
IGFD::FileDialogConfig config;
|
||||
config.path = ".";
|
||||
config.countSelectionMax = 1;
|
||||
config.flags = ImGuiFileDialogFlags_Modal;
|
||||
|
||||
ImGuiFileDialog::Instance()->OpenDialog("ChooseLibraryDirDialog", "Choose a library directory", nullptr, config);
|
||||
}
|
||||
|
||||
if (ImGui::Button(ICON_MDI_FOLDER " Export to device"))
|
||||
{
|
||||
IGFD::FileDialogConfig config;
|
||||
|
|
@ -388,6 +378,16 @@ void LibraryWindow::Draw()
|
|||
ImGuiFileDialog::Instance()->OpenDialog("ChooseOutputDirDialog", "Choose an output directory", nullptr, config);
|
||||
}
|
||||
|
||||
if (ImGui::Button( ICON_MDI_FOLDER " Select directory"))
|
||||
{
|
||||
IGFD::FileDialogConfig config;
|
||||
config.path = ".";
|
||||
config.countSelectionMax = 1;
|
||||
config.flags = ImGuiFileDialogFlags_Modal;
|
||||
|
||||
ImGuiFileDialog::Instance()->OpenDialog("ChooseLibraryDirDialog", "Choose a library directory", nullptr, config);
|
||||
}
|
||||
|
||||
if (!m_libraryManager.IsInitialized())
|
||||
{
|
||||
ImGui::SameLine();
|
||||
|
|
@ -471,7 +471,6 @@ void LibraryWindow::Draw()
|
|||
ImGui::TableHeader(ImGui::TableGetColumnName(2));
|
||||
|
||||
int internal_id = 1;
|
||||
uint32_t row = 0;
|
||||
for (auto &p : m_libraryManager)
|
||||
{
|
||||
ImGui::TableNextColumn();
|
||||
|
|
@ -480,19 +479,15 @@ void LibraryWindow::Draw()
|
|||
ImGui::PushID(internal_id++);
|
||||
|
||||
// Select
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
bool state = p->IsSelected();
|
||||
ImGui::Checkbox("", &state);
|
||||
ImGui::Checkbox("", p->Selected());
|
||||
|
||||
if (!state && select_all)
|
||||
if (!p->IsSelected() && select_all)
|
||||
{
|
||||
select_all = false; // Désélectionner "Select All" si un des items est désélectionné
|
||||
}
|
||||
|
||||
row++;
|
||||
ImGui::TableNextColumn();
|
||||
|
||||
if (ImGui::SmallButton("Load"))
|
||||
{
|
||||
m_storyManager.OpenProject(p->GetUuid());
|
||||
|
|
|
|||
|
|
@ -446,6 +446,8 @@ void MainWindow::NewProjectPopup()
|
|||
|
||||
if (!std::filesystem::is_directory(projdir))
|
||||
{
|
||||
CloseProject();
|
||||
|
||||
m_story = m_libraryManager.NewProject();
|
||||
|
||||
if (m_story)
|
||||
|
|
@ -608,25 +610,22 @@ void MainWindow::ProjectPropertiesPopup()
|
|||
void MainWindow::SaveProject()
|
||||
{
|
||||
nlohmann::json model;
|
||||
m_nodeEditorWindow.Save(model);
|
||||
m_story->Save(model, m_resources);
|
||||
m_story->Save(m_resources);
|
||||
}
|
||||
|
||||
void MainWindow::OpenProject(const std::string &uuid)
|
||||
{
|
||||
CloseProject();
|
||||
nlohmann::json model;
|
||||
|
||||
m_story = m_libraryManager.GetStory(uuid);
|
||||
|
||||
if (!m_story)
|
||||
{
|
||||
Log("Cannot find story: " + uuid);
|
||||
}
|
||||
else if (m_story->Load(model, m_resources))
|
||||
else if (m_story->Load(m_resources))
|
||||
{
|
||||
Log("Open project success");
|
||||
m_nodeEditorWindow.Load(model);
|
||||
m_nodeEditorWindow.Load(m_story);
|
||||
auto proj = m_story->GetProjectFilePath();
|
||||
// Add to recent if not exists
|
||||
if (std::find(m_recentProjects.begin(), m_recentProjects.end(), proj) == m_recentProjects.end())
|
||||
|
|
@ -682,6 +681,7 @@ void MainWindow::CloseProject()
|
|||
if (m_story)
|
||||
{
|
||||
m_story->Clear();
|
||||
m_story.reset();
|
||||
}
|
||||
|
||||
m_resources.Clear();
|
||||
|
|
@ -812,6 +812,11 @@ void MainWindow::DeleteResource(FilterIterator &it)
|
|||
return m_resources.Delete(it);
|
||||
}
|
||||
|
||||
std::shared_ptr<BaseNode> MainWindow::CreateNode(const std::string &type)
|
||||
{
|
||||
return m_story->CreateNode(type);
|
||||
}
|
||||
|
||||
void MainWindow::Build(bool compileonly)
|
||||
{
|
||||
// 1. First compile nodes to assembly
|
||||
|
|
@ -829,15 +834,19 @@ void MainWindow::Build(bool compileonly)
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
std::string MainWindow::GetNodeEntryLabel(const std::string &nodeId)
|
||||
void MainWindow::DeleteNode(const std::string &id)
|
||||
{
|
||||
return m_nodeEditorWindow.GetNodeEntryLabel(nodeId);
|
||||
m_story->DeleteNode(id);
|
||||
}
|
||||
|
||||
void MainWindow::DeleteLink(std::shared_ptr<Connection> c)
|
||||
{
|
||||
m_story->DeleteLink(c);
|
||||
}
|
||||
|
||||
std::list<std::shared_ptr<Connection>> MainWindow::GetNodeConnections(const std::string &nodeId)
|
||||
{
|
||||
return m_nodeEditorWindow.GetNodeConnections(nodeId);
|
||||
return m_story->GetNodeConnections(nodeId);
|
||||
}
|
||||
|
||||
bool MainWindow::CompileToAssembler()
|
||||
|
|
@ -846,7 +855,7 @@ bool MainWindow::CompileToAssembler()
|
|||
// FIXME
|
||||
|
||||
// 2. Generate the assembly code from the model
|
||||
bool ret = m_nodeEditorWindow.Build(m_currentCode);
|
||||
bool ret = m_story->Build(m_currentCode);
|
||||
|
||||
// Add global functions
|
||||
if (ret)
|
||||
|
|
|
|||
|
|
@ -134,9 +134,13 @@ private:
|
|||
virtual void ClearResources() override;
|
||||
virtual std::pair<FilterIterator, FilterIterator> Resources() override;
|
||||
virtual void DeleteResource(FilterIterator &it) override;
|
||||
|
||||
virtual std::shared_ptr<BaseNode> CreateNode(const std::string &type) override;
|
||||
virtual void Build(bool compileonly) override;
|
||||
virtual void DeleteNode(const std::string &id) override;
|
||||
virtual void DeleteLink(std::shared_ptr<Connection> c) 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 Stop() override;
|
||||
|
|
|
|||
|
|
@ -5,14 +5,18 @@
|
|||
|
||||
unsigned long BaseNodeWidget::s_nextId = 1;
|
||||
|
||||
BaseNodeWidget::BaseNodeWidget(const std::string &type, IStoryManager &proj)
|
||||
: BaseNode(type)
|
||||
, m_story(proj)
|
||||
BaseNodeWidget::BaseNodeWidget(IStoryManager &manager, std::shared_ptr<BaseNode> base)
|
||||
: m_manager(manager)
|
||||
, m_base(base)
|
||||
{
|
||||
// m_id = UUID().String();
|
||||
m_node = std::make_unique<Node>(GetNextId(), ""); // ImGui internal ID
|
||||
}
|
||||
|
||||
BaseNodeWidget::~BaseNodeWidget()
|
||||
{
|
||||
std::cout << "Deleted node widget" << std::endl;
|
||||
}
|
||||
|
||||
void BaseNodeWidget::AddInput()
|
||||
{
|
||||
m_node->Inputs.emplace_back(GetNextId(), "", PinType::Flow);
|
||||
|
|
@ -59,10 +63,13 @@ void BaseNodeWidget::FrameStart()
|
|||
ed::BeginNode(m_node->ID);
|
||||
|
||||
if (m_firstFrame)
|
||||
{
|
||||
if (m_base)
|
||||
{
|
||||
// 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()));
|
||||
ed::SetNodePosition(m_node->ID, ImVec2(m_base->GetX(), m_base->GetY()));
|
||||
}
|
||||
}
|
||||
m_firstFrame = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,24 +95,16 @@ struct Link
|
|||
* @brief Basically a wrapper class around ImGuiNodeEditor Node structure
|
||||
*
|
||||
*/
|
||||
class BaseNodeWidget : public BaseNode
|
||||
class BaseNodeWidget
|
||||
{
|
||||
public:
|
||||
struct NodePosition
|
||||
{
|
||||
float x;
|
||||
float y;
|
||||
};
|
||||
|
||||
BaseNodeWidget(const std::string &type, IStoryManager &proj);
|
||||
BaseNodeWidget(IStoryManager &manager, std::shared_ptr<BaseNode> base);
|
||||
virtual ~BaseNodeWidget();
|
||||
|
||||
virtual void Initialize();
|
||||
|
||||
virtual void Draw() = 0;
|
||||
virtual void DrawProperties() = 0;
|
||||
virtual std::string GenerateConstants() = 0;
|
||||
virtual std::string Build() = 0;
|
||||
virtual std::string GetEntryLabel() = 0;
|
||||
|
||||
|
||||
void FrameStart();
|
||||
|
|
@ -201,14 +193,16 @@ public:
|
|||
void SetOutputs(uint32_t num);
|
||||
void DeleteOutput();
|
||||
|
||||
std::shared_ptr<BaseNode> Base() { return m_base; }
|
||||
|
||||
private:
|
||||
IStoryManager &m_story;
|
||||
IStoryManager &m_manager;
|
||||
std::shared_ptr<BaseNode> m_base;
|
||||
|
||||
std::unique_ptr<Node> m_node;
|
||||
|
||||
bool m_firstFrame{true};
|
||||
|
||||
static unsigned long s_nextId;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,339 +0,0 @@
|
|||
|
||||
#include <sstream>
|
||||
#include "media_node.h"
|
||||
|
||||
namespace ed = ax::NodeEditor;
|
||||
#include "IconsMaterialDesignIcons.h"
|
||||
#include "story_project.h"
|
||||
|
||||
MediaNode::MediaNode(const std::string &title, IStoryManager &proj)
|
||||
: BaseNodeWidget(title, proj)
|
||||
, m_story(proj)
|
||||
{
|
||||
// Create defaut one input and one output
|
||||
AddInput();
|
||||
AddOutputs(1);
|
||||
|
||||
|
||||
std::string widgetId = std::to_string(GetInternalId()); // Make widget unique by using the node ID
|
||||
|
||||
m_buttonUniqueName = "Play " ICON_MDI_PLAY "##id" + widgetId;
|
||||
}
|
||||
|
||||
void MediaNode::Draw()
|
||||
{
|
||||
BaseNodeWidget::FrameStart();
|
||||
|
||||
|
||||
static ImGuiTableFlags flags = ImGuiTableFlags_Borders |
|
||||
ImGuiTableFlags_NoHostExtendX | ImGuiTableFlags_SizingFixedFit;
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(10.0f, 10.0f));
|
||||
if (ImGui::BeginTable("table1", 1, flags))
|
||||
{
|
||||
ImGui::TableNextRow();
|
||||
ImU32 bg_color = ImGui::GetColorU32(ImVec4(0.3f, 0.3f, 0.7f, 1.0f));
|
||||
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, bg_color);
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::TextUnformatted("Media node");
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
|
||||
if (m_image.Valid())
|
||||
{
|
||||
ImGui::Image(m_image.texture, ImVec2(320, 240));
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui::Dummy(ImVec2(320, 240));
|
||||
}
|
||||
|
||||
// Use AlignTextToFramePadding() to align text baseline to the baseline of framed elements
|
||||
// (otherwise a Text+SameLine+Button sequence will have the text a little too high by default)
|
||||
|
||||
|
||||
|
||||
// Use AlignTextToFramePadding() to align text baseline to the baseline of framed elements
|
||||
// (otherwise a Text+SameLine+Button sequence will have the text a little too high by default)
|
||||
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::Text("Outputs:");
|
||||
ImGui::SameLine();
|
||||
|
||||
// Arrow buttons with Repeater
|
||||
|
||||
uint32_t counter = Outputs();
|
||||
float spacing = ImGui::GetStyle().ItemInnerSpacing.x;
|
||||
ImGui::PushButtonRepeat(true);
|
||||
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" + GetId();
|
||||
if (ImGui::ArrowButton(rightSingle.c_str(), ImGuiDir_Right))
|
||||
{
|
||||
counter++;
|
||||
}
|
||||
ImGui::PopButtonRepeat();
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("%d", counter);
|
||||
|
||||
SetOutputs(counter);
|
||||
|
||||
DrawPins();
|
||||
|
||||
BaseNodeWidget::FrameEnd();
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
"internal-data": {
|
||||
"image": "fairy.png",
|
||||
"sound": "la_fee_luminelle.mp3"
|
||||
},
|
||||
*/
|
||||
void MediaNode::Initialize()
|
||||
{
|
||||
BaseNodeWidget::Initialize();
|
||||
nlohmann::json j = GetInternalData();
|
||||
SetImage(j["image"].get<std::string>());
|
||||
SetSound(j["sound"].get<std::string>());
|
||||
}
|
||||
|
||||
void MediaNode::StoreInternalData()
|
||||
{
|
||||
nlohmann::json j;
|
||||
j["image"] = m_image.name;
|
||||
j["sound"] = m_soundName;
|
||||
|
||||
SetInternalData(j);
|
||||
}
|
||||
|
||||
void MediaNode::DrawProperties()
|
||||
{
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::Text("Image");
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::Text("%s", m_image.name.c_str());
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
static bool isImage = true;
|
||||
if (ImGui::Button("Select...##image")) {
|
||||
ImGui::OpenPopup("popup_button");
|
||||
isImage = true;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(ICON_MDI_CLOSE_BOX_OUTLINE "##delimage")) {
|
||||
SetImage("");
|
||||
}
|
||||
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::Text("Sound");
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::Text("%s", m_soundName.c_str());
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button(m_buttonUniqueName.c_str()))
|
||||
{
|
||||
m_story.PlaySoundFile(m_soundPath);
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("Select...##sound")) {
|
||||
ImGui::OpenPopup("popup_button");
|
||||
isImage = false;
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(ICON_MDI_CLOSE_BOX_OUTLINE "##delsound")) {
|
||||
SetSound("");
|
||||
}
|
||||
|
||||
// This is the actual popup Gui drawing section.
|
||||
if (ImGui::BeginPopup("popup_button")) {
|
||||
ImGui::SeparatorText(isImage ? "Images" : "Sounds");
|
||||
|
||||
auto [filtreDebut, filtreFin] = isImage ? m_story.Images() : m_story.Sounds();
|
||||
int n = 0;
|
||||
for (auto it = filtreDebut; it != filtreFin; ++it, n++)
|
||||
{
|
||||
if (ImGui::Selectable((*it)->file.c_str()))
|
||||
{
|
||||
if (isImage)
|
||||
{
|
||||
SetImage((*it)->file);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetSound((*it)->file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndPopup(); // Note this does not do anything to the popup open/close state. It just terminates the content declaration.
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
std::string MediaNode::ChoiceLabel() const
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "mediaChoice" << std::setw(4) << std::setfill('0') << GetId();
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string MediaNode::GetEntryLabel()
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << ".mediaEntry" << std::setw(4) << std::setfill('0') << GetId();
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
|
||||
std::string MediaNode::GenerateConstants()
|
||||
{
|
||||
std::string s;
|
||||
|
||||
if (m_image.name.size() > 0)
|
||||
{
|
||||
s = StoryProject::FileToConstant(m_image.name, ".qoi"); // FIXME: Generate the extension setup in user option of output format
|
||||
}
|
||||
if (m_soundName.size() > 0)
|
||||
{
|
||||
s += StoryProject::FileToConstant(m_soundName, ".wav"); // FIXME: Generate the extension setup in user option of output format
|
||||
}
|
||||
|
||||
int nb_out_conns = Outputs();
|
||||
if (nb_out_conns > 1)
|
||||
{
|
||||
// Generate choice table if needed (out ports > 1)
|
||||
std::stringstream ss;
|
||||
std::string label = ChoiceLabel();
|
||||
ss << "$" << label
|
||||
<< " DC32, "
|
||||
<< nb_out_conns << ", ";
|
||||
|
||||
std::list<std::shared_ptr<Connection>> conns = m_story.GetNodeConnections(GetId());
|
||||
int i = 0;
|
||||
for (auto & c : conns)
|
||||
{
|
||||
std::stringstream ssChoice;
|
||||
|
||||
// On va chercher le label d'entrée du noeud connecté à l'autre bout
|
||||
ss << m_story.GetNodeEntryLabel(c->inNodeId);
|
||||
if (i < (nb_out_conns - 1))
|
||||
{
|
||||
ss << ", ";
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << "\n";
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
s += ss.str();
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
std::string MediaNode::Build()
|
||||
{
|
||||
std::stringstream ss;
|
||||
int nb_out_conns = Outputs();
|
||||
|
||||
ss << R"(; ---------------------------- )"
|
||||
<< GetTitle()
|
||||
<< " Type: "
|
||||
<< (nb_out_conns == 0 ? "End" : nb_out_conns == 1 ? "Transition" : "Choice")
|
||||
<< "\n";
|
||||
std::string image = StoryProject::RemoveFileExtension(m_image.name);
|
||||
std::string sound = StoryProject::RemoveFileExtension(m_soundName);
|
||||
|
||||
// Le label de ce noeud est généré de la façon suivante :
|
||||
// "media" + Node ID + id du noeud parent. Si pas de noeud parent, alors rien
|
||||
ss << GetEntryLabel() << ":\n";
|
||||
|
||||
if (image.size() > 0)
|
||||
{
|
||||
ss << "lcons r0, $" << image << "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << "lcons r0, 0\n";
|
||||
}
|
||||
|
||||
if (sound.size() > 0)
|
||||
{
|
||||
ss << "lcons r1, $" << sound << "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << "lcons r1, 0\n";
|
||||
}
|
||||
// Call the media executor (image, sound)
|
||||
ss << "syscall 1\n";
|
||||
|
||||
// Check output connections number
|
||||
// == 0: end node : generate halt
|
||||
// == 1: transition node : image + sound on demand, jump directly to the other node when OK
|
||||
// > 1 : choice node : call the node choice manager
|
||||
|
||||
if (nb_out_conns == 0) // End node
|
||||
{
|
||||
ss << "halt\n";
|
||||
}
|
||||
else if (nb_out_conns == 1) // it is a transition node
|
||||
{
|
||||
std::list<std::shared_ptr<Connection>> conns = m_story.GetNodeConnections(GetId());
|
||||
|
||||
|
||||
for (auto c : conns)
|
||||
{
|
||||
if (c->outNodeId == GetId())
|
||||
{
|
||||
// On place dans R0 le prochain noeud à exécuter en cas de OK
|
||||
ss << "lcons r0, "
|
||||
<< m_story.GetNodeEntryLabel(c->inNodeId) << "\n"
|
||||
<< "ret\n";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else // Choice node
|
||||
{
|
||||
ss << "lcons r0, $" << ChoiceLabel() << "\n"
|
||||
<< "jump .media ; no return possible, so a jump is enough";
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
204
story-editor/src/node_editor/media_node_widget.cpp
Normal file
204
story-editor/src/node_editor/media_node_widget.cpp
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
|
||||
#include <sstream>
|
||||
#include "media_node_widget.h"
|
||||
|
||||
namespace ed = ax::NodeEditor;
|
||||
#include "IconsMaterialDesignIcons.h"
|
||||
#include "story_project.h"
|
||||
|
||||
MediaNodeWidget::MediaNodeWidget(IStoryManager &manager, std::shared_ptr<BaseNode> node)
|
||||
: BaseNodeWidget(manager, node)
|
||||
, m_manager(manager)
|
||||
{
|
||||
m_mediaNode = std::dynamic_pointer_cast<MediaNode>(node);
|
||||
|
||||
// Create defaut one input and one output
|
||||
AddInput();
|
||||
AddOutputs(1);
|
||||
|
||||
std::string widgetId = std::to_string(GetInternalId()); // Make widget unique by using the node ID
|
||||
|
||||
m_buttonUniqueName = "Play " ICON_MDI_PLAY "##id" + widgetId;
|
||||
}
|
||||
|
||||
void MediaNodeWidget::Draw()
|
||||
{
|
||||
BaseNodeWidget::FrameStart();
|
||||
|
||||
|
||||
static ImGuiTableFlags flags = ImGuiTableFlags_Borders |
|
||||
ImGuiTableFlags_NoHostExtendX | ImGuiTableFlags_SizingFixedFit;
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(10.0f, 10.0f));
|
||||
if (ImGui::BeginTable("table1", 1, flags))
|
||||
{
|
||||
ImGui::TableNextRow();
|
||||
ImU32 bg_color = ImGui::GetColorU32(ImVec4(0.3f, 0.3f, 0.7f, 1.0f));
|
||||
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, bg_color);
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::TextUnformatted("Media node");
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
|
||||
if (m_image.Valid())
|
||||
{
|
||||
ImGui::Image(m_image.texture, ImVec2(320, 240));
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui::Dummy(ImVec2(320, 240));
|
||||
}
|
||||
|
||||
// Use AlignTextToFramePadding() to align text baseline to the baseline of framed elements
|
||||
// (otherwise a Text+SameLine+Button sequence will have the text a little too high by default)
|
||||
|
||||
|
||||
|
||||
// Use AlignTextToFramePadding() to align text baseline to the baseline of framed elements
|
||||
// (otherwise a Text+SameLine+Button sequence will have the text a little too high by default)
|
||||
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::Text("Outputs:");
|
||||
ImGui::SameLine();
|
||||
|
||||
// Arrow buttons with Repeater
|
||||
|
||||
uint32_t counter = Outputs();
|
||||
float spacing = ImGui::GetStyle().ItemInnerSpacing.x;
|
||||
ImGui::PushButtonRepeat(true);
|
||||
std::string leftSingle = "##left" + m_mediaNode->GetId();
|
||||
if (ImGui::ArrowButton(leftSingle.c_str(), ImGuiDir_Left)) { if (counter > 1) counter--; }
|
||||
ImGui::SameLine(0.0f, spacing);
|
||||
|
||||
std::string rightSingle = "##right" + m_mediaNode->GetId();
|
||||
if (ImGui::ArrowButton(rightSingle.c_str(), ImGuiDir_Right))
|
||||
{
|
||||
counter++;
|
||||
}
|
||||
ImGui::PopButtonRepeat();
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("%d", counter);
|
||||
|
||||
SetOutputs(counter);
|
||||
|
||||
DrawPins();
|
||||
|
||||
BaseNodeWidget::FrameEnd();
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
"internal-data": {
|
||||
"image": "fairy.png",
|
||||
"sound": "la_fee_luminelle.mp3"
|
||||
},
|
||||
*/
|
||||
void MediaNodeWidget::Initialize()
|
||||
{
|
||||
BaseNodeWidget::Initialize();
|
||||
nlohmann::json j = m_mediaNode->GetInternalData();
|
||||
SetImage(j["image"].get<std::string>());
|
||||
SetSound(j["sound"].get<std::string>());
|
||||
}
|
||||
|
||||
|
||||
void MediaNodeWidget::StoreInternalData()
|
||||
{
|
||||
nlohmann::json j;
|
||||
j["image"] = m_mediaNode->image;
|
||||
j["sound"] = m_mediaNode->sound;
|
||||
|
||||
m_mediaNode->SetInternalData(j);
|
||||
}
|
||||
|
||||
void MediaNodeWidget::DrawProperties()
|
||||
{
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::Text("Image");
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::Text("%s", m_mediaNode->image.c_str());
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
static bool isImage = true;
|
||||
if (ImGui::Button("Select...##image")) {
|
||||
ImGui::OpenPopup("popup_button");
|
||||
isImage = true;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(ICON_MDI_CLOSE_BOX_OUTLINE "##delimage")) {
|
||||
SetImage("");
|
||||
}
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::Text("Sound");
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::Text("%s", m_mediaNode->sound.c_str());
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button(m_buttonUniqueName.c_str()))
|
||||
{
|
||||
m_manager.PlaySoundFile(m_soundPath);
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("Select...##sound")) {
|
||||
ImGui::OpenPopup("popup_button");
|
||||
isImage = false;
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(ICON_MDI_CLOSE_BOX_OUTLINE "##delsound")) {
|
||||
SetSound("");
|
||||
}
|
||||
|
||||
// This is the actual popup Gui drawing section.
|
||||
if (ImGui::BeginPopup("popup_button")) {
|
||||
ImGui::SeparatorText(isImage ? "Images" : "Sounds");
|
||||
|
||||
auto [filtreDebut, filtreFin] = isImage ? m_manager.Images() : m_manager.Sounds();
|
||||
int n = 0;
|
||||
for (auto it = filtreDebut; it != filtreFin; ++it, n++)
|
||||
{
|
||||
if (ImGui::Selectable((*it)->file.c_str()))
|
||||
{
|
||||
if (isImage)
|
||||
{
|
||||
SetImage((*it)->file);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetSound((*it)->file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndPopup(); // Note this does not do anything to the popup open/close state. It just terminates the content declaration.
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void MediaNodeWidget::SetImage(const std::string &f)
|
||||
{
|
||||
m_mediaNode->image = f;
|
||||
m_image.Load(m_manager.BuildFullAssetsPath(f));
|
||||
StoreInternalData();
|
||||
}
|
||||
|
||||
void MediaNodeWidget::SetSound(const std::string &f)
|
||||
{
|
||||
m_mediaNode->sound = f;
|
||||
m_soundPath = m_manager.BuildFullAssetsPath(m_mediaNode->sound);
|
||||
StoreInternalData();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -7,34 +7,33 @@
|
|||
|
||||
#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 MediaNode : public BaseNodeWidget
|
||||
class MediaNodeWidget : public BaseNodeWidget
|
||||
{
|
||||
public:
|
||||
MediaNode(const std::string &title, IStoryManager &proj);
|
||||
MediaNodeWidget(IStoryManager &manager, std::shared_ptr<BaseNode> node);
|
||||
|
||||
void Draw() 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;
|
||||
IStoryManager &m_manager;
|
||||
std::shared_ptr<MediaNode> m_mediaNode;
|
||||
Gui::Image m_image;
|
||||
std::string m_soundName;
|
||||
|
||||
std::string m_soundPath;
|
||||
std::string m_buttonUniqueName;
|
||||
|
||||
|
||||
void SetImage(const std::string &f);
|
||||
void SetSound(const std::string &f);
|
||||
std::string ChoiceLabel() const;
|
||||
|
||||
void StoreInternalData();
|
||||
|
||||
};
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
#include <sstream>
|
||||
#include "IconsFontAwesome5_c.h"
|
||||
|
||||
#include "media_node.h"
|
||||
#include "media_node_widget.h"
|
||||
#include "gui.h"
|
||||
#include "uuid.h"
|
||||
|
||||
|
|
@ -21,12 +21,12 @@ if (!(x)) { \
|
|||
#include "json.hpp"
|
||||
|
||||
|
||||
NodeEditorWindow::NodeEditorWindow(IStoryManager &proj)
|
||||
NodeEditorWindow::NodeEditorWindow(IStoryManager &manager)
|
||||
: WindowBase("Node editor")
|
||||
, m_story(proj)
|
||||
, m_manager(manager)
|
||||
{
|
||||
|
||||
registerNode<MediaNode>("media-node");
|
||||
registerNode<MediaNodeWidget>("media-node");
|
||||
}
|
||||
|
||||
NodeEditorWindow::~NodeEditorWindow()
|
||||
|
|
@ -46,21 +46,17 @@ void NodeEditorWindow::Initialize()
|
|||
void NodeEditorWindow::Clear()
|
||||
{
|
||||
m_nodes.clear();
|
||||
m_links.clear();
|
||||
m_story.reset();
|
||||
}
|
||||
|
||||
std::string NodeEditorWindow::GenerateNodeId()
|
||||
{
|
||||
return Uuid().String();
|
||||
}
|
||||
|
||||
|
||||
ed::PinId NodeEditorWindow::GetInputPin(const std::string &modelNodeId, int pinIndex)
|
||||
{
|
||||
ed::PinId id = 0;
|
||||
|
||||
for (auto & n : m_nodes)
|
||||
{
|
||||
if (n->GetId() == modelNodeId)
|
||||
if (n->Base()->GetId() == modelNodeId)
|
||||
{
|
||||
id = n->GetInputPinAt(pinIndex);
|
||||
break;
|
||||
|
|
@ -81,7 +77,7 @@ ed::PinId NodeEditorWindow::GetOutputPin(const std::string &modelNodeId, int pin
|
|||
|
||||
for (auto & n : m_nodes)
|
||||
{
|
||||
if (n->GetId() == modelNodeId)
|
||||
if (n->Base()->GetId() == modelNodeId)
|
||||
{
|
||||
id = n->GetOutputPinAt(pinIndex);
|
||||
break;
|
||||
|
|
@ -96,75 +92,41 @@ ed::PinId NodeEditorWindow::GetOutputPin(const std::string &modelNodeId, int pin
|
|||
return id;
|
||||
}
|
||||
|
||||
void NodeEditorWindow::Load(const nlohmann::json &model)
|
||||
void NodeEditorWindow::Load(std::shared_ptr<StoryProject> story)
|
||||
{
|
||||
try {
|
||||
m_story = story;
|
||||
|
||||
nlohmann::json nodesJsonArray = model["nodes"];
|
||||
if (m_story)
|
||||
{
|
||||
try {
|
||||
|
||||
BaseNodeWidget::InitId();
|
||||
m_nodes.clear();
|
||||
m_links.clear();
|
||||
|
||||
for (auto& element : nodesJsonArray) {
|
||||
auto [node_begin, node_end] = m_story->Nodes();
|
||||
|
||||
std::string type = element["type"].get<std::string>();
|
||||
|
||||
auto n = createNode(type, m_story);
|
||||
for (auto it = node_begin; it != node_end; ++it)
|
||||
{
|
||||
auto n = CreateNodeWidget((*it)->GetType(), m_manager, (*it));
|
||||
if (n)
|
||||
{
|
||||
n->FromJson(element);
|
||||
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);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::logic_error(std::string("No registered model with name ") + type);
|
||||
throw std::logic_error(std::string("No registered model with name ") + (*it)->GetType());
|
||||
}
|
||||
}
|
||||
auto [link_begin, link_end] = m_story->Links();
|
||||
|
||||
std::cout << model.dump(4) << std::endl;
|
||||
|
||||
// Ici on reste flexible sur les connexions, cela permet de créer éventuellement des
|
||||
// projets sans fils (bon, l'élément devrait quand même exister dans le JSON)
|
||||
if (model.contains("connections"))
|
||||
for (auto it = link_begin; it != link_end; ++it)
|
||||
{
|
||||
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));
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
CreateLink(*it,
|
||||
GetInputPin((*it)->inNodeId, (*it)->inPortIndex),
|
||||
GetOutputPin((*it)->outNodeId, (*it)->outPortIndex));
|
||||
}
|
||||
|
||||
m_loaded = true;
|
||||
|
|
@ -173,15 +135,16 @@ void NodeEditorWindow::Load(const nlohmann::json &model)
|
|||
{
|
||||
std::cout << "(NodeEditorWindow::Load) " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void NodeEditorWindow::CreateLink(const 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>();
|
||||
|
||||
*conn->model = model;
|
||||
conn->model = model;
|
||||
|
||||
// ImGui stuff for links
|
||||
conn->ed_link->Id = BaseNodeWidget::GetNextId();
|
||||
|
|
@ -192,159 +155,30 @@ void NodeEditorWindow::CreateLink(const Connection &model, ed::PinId inId, ed::P
|
|||
m_links.push_back(conn);
|
||||
}
|
||||
|
||||
void NodeEditorWindow::Save(nlohmann::json &model)
|
||||
|
||||
|
||||
std::shared_ptr<Connection> NodeEditorWindow::LinkToModel(ed::PinId InputId, ed::PinId OutputId)
|
||||
{
|
||||
ed::SetCurrentEditor(m_context);
|
||||
// Save nodes
|
||||
|
||||
nlohmann::json nodes = nlohmann::json::array();
|
||||
for (const auto & n : m_nodes)
|
||||
{
|
||||
nodes.push_back(n->ToJson());
|
||||
}
|
||||
|
||||
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;
|
||||
ed::SetCurrentEditor(nullptr);
|
||||
}
|
||||
|
||||
Connection NodeEditorWindow::LinkToModel(ed::PinId InputId, ed::PinId OutputId)
|
||||
{
|
||||
Connection c;
|
||||
auto c = std::make_shared<Connection>();
|
||||
int index;
|
||||
for (const auto & n : m_nodes)
|
||||
{
|
||||
if (n->HasOnputPinId(OutputId, index))
|
||||
{
|
||||
c.outNodeId = n->GetId();
|
||||
c.outPortIndex = index;
|
||||
c->outNodeId = n->Base()->GetId();
|
||||
c->outPortIndex = index;
|
||||
}
|
||||
|
||||
if (n->HasInputPinId(InputId, index))
|
||||
{
|
||||
c.inNodeId = n->GetId();
|
||||
c.inPortIndex = index;
|
||||
c->inNodeId = n->Base()->GetId();
|
||||
c->inPortIndex = index;
|
||||
}
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
std::string NodeEditorWindow::FindFirstNode() const
|
||||
{
|
||||
std::string id;
|
||||
|
||||
// First node is the one without connection on its input port
|
||||
|
||||
for (const auto & n : m_nodes)
|
||||
{
|
||||
bool foundConnection = false;
|
||||
|
||||
for (const auto& l : m_links)
|
||||
{
|
||||
if (l->model->inNodeId == n->GetId())
|
||||
{
|
||||
foundConnection = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundConnection)
|
||||
{
|
||||
id = n->GetId();
|
||||
m_story.Log("First node is: " + id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
bool NodeEditorWindow::Build(std::string &codeStr)
|
||||
{
|
||||
std::stringstream code;
|
||||
ed::SetCurrentEditor(m_context);
|
||||
|
||||
|
||||
std::stringstream chip32;
|
||||
|
||||
std::string firstNode = FindFirstNode();
|
||||
|
||||
if (firstNode == "")
|
||||
{
|
||||
m_story.Log("First node not found, there must be only one node with a free input.", true);
|
||||
return false;
|
||||
}
|
||||
|
||||
code << "\tjump " << GetNodeEntryLabel(firstNode) << "\r\n";
|
||||
|
||||
// First generate all constants
|
||||
for (const auto & n : m_nodes)
|
||||
{
|
||||
code << n->GenerateConstants() << "\n";
|
||||
}
|
||||
|
||||
for (const auto & n : m_nodes)
|
||||
{
|
||||
code << n->Build() << "\n";
|
||||
}
|
||||
|
||||
ed::SetCurrentEditor(nullptr);
|
||||
codeStr = code.str();
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
ed::SetCurrentEditor(nullptr);
|
||||
return c;
|
||||
}
|
||||
|
||||
std::string NodeEditorWindow::GetNodeEntryLabel(const std::string &nodeId)
|
||||
{
|
||||
std::string label;
|
||||
ed::SetCurrentEditor(m_context);
|
||||
|
||||
for (const auto & n : m_nodes)
|
||||
{
|
||||
if (n->GetId() == nodeId)
|
||||
{
|
||||
label = n->GetEntryLabel();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ed::SetCurrentEditor(nullptr);
|
||||
return label;
|
||||
}
|
||||
|
||||
|
||||
std::shared_ptr<BaseNodeWidget> NodeEditorWindow::GetSelectedNode()
|
||||
{
|
||||
|
|
@ -417,9 +251,10 @@ void NodeEditorWindow::Draw()
|
|||
// ed::AcceptNewItem() return true when user release mouse button.
|
||||
if (ed::AcceptNewItem())
|
||||
{
|
||||
Connection model = LinkToModel(inputPinId, outputPinId);
|
||||
auto c = LinkToModel(inputPinId, outputPinId);
|
||||
m_story->AddConnection(c);
|
||||
|
||||
CreateLink(model, inputPinId, outputPinId);
|
||||
CreateLink(c, inputPinId, outputPinId);
|
||||
|
||||
// Draw new link.
|
||||
ed::Link(m_links.back()->ed_link->Id, inputPinId, outputPinId);
|
||||
|
|
@ -442,9 +277,13 @@ void NodeEditorWindow::Draw()
|
|||
{
|
||||
if (ed::AcceptDeletedItem())
|
||||
{
|
||||
auto id = std::find_if(m_nodes.begin(), m_nodes.end(), [nodeId](std::shared_ptr<BaseNodeWidget> node) { return node->GetInternalId() == nodeId.Get(); });
|
||||
if (id != m_nodes.end())
|
||||
m_nodes.erase(id);
|
||||
auto it = std::find_if(m_nodes.begin(), m_nodes.end(), [nodeId](std::shared_ptr<BaseNodeWidget> node) { return node->GetInternalId() == nodeId.Get(); });
|
||||
if (it != m_nodes.end())
|
||||
{
|
||||
// First delete model, then current entry
|
||||
m_manager.DeleteNode((*it)->Base()->GetId());
|
||||
m_nodes.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -457,12 +296,13 @@ void NodeEditorWindow::Draw()
|
|||
if (ed::AcceptDeletedItem())
|
||||
{
|
||||
|
||||
m_links.erase(std::remove_if(m_links.begin(),
|
||||
m_links.end(),
|
||||
[deletedLinkId](std::shared_ptr<LinkInfo> inf)
|
||||
auto it = std::find_if(m_links.begin(), m_links.end(), [deletedLinkId](std::shared_ptr<LinkInfo> inf) { return inf->ed_link->Id == deletedLinkId; });
|
||||
if (it != m_links.end())
|
||||
{
|
||||
return inf->ed_link->Id == deletedLinkId;
|
||||
}));
|
||||
// First delete model, then current entry
|
||||
m_manager.DeleteLink((*it)->model);
|
||||
m_links.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
// You may reject link deletion by calling:
|
||||
|
|
@ -486,15 +326,18 @@ void NodeEditorWindow::Draw()
|
|||
Node* node = nullptr;
|
||||
if (ImGui::MenuItem("Media Node"))
|
||||
{
|
||||
auto n = createNode("media-node", m_story);
|
||||
auto base = m_manager.CreateNode("media-node");
|
||||
if (base)
|
||||
{
|
||||
auto n = CreateNodeWidget(base->GetType(), m_manager, base);
|
||||
if (n)
|
||||
{
|
||||
n->SetId(GenerateNodeId());
|
||||
n->SetPosition(newNodePostion.x, newNodePostion.y);
|
||||
n->Base()->SetPosition(newNodePostion.x, newNodePostion.y);
|
||||
n->Initialize();
|
||||
m_nodes.push_back(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
#include "window_base.h"
|
||||
#include "i_story_manager.h"
|
||||
#include "json.hpp"
|
||||
#include "story_project.h"
|
||||
|
||||
|
||||
namespace ed = ax::NodeEditor;
|
||||
|
|
@ -46,28 +47,24 @@ public:
|
|||
std::shared_ptr<Connection> model;
|
||||
};
|
||||
|
||||
NodeEditorWindow(IStoryManager &proj);
|
||||
NodeEditorWindow(IStoryManager &manager);
|
||||
~NodeEditorWindow();
|
||||
virtual void Draw() override;
|
||||
|
||||
void Initialize();
|
||||
void Clear();
|
||||
void Load(const nlohmann::json &model);
|
||||
void Save(nlohmann::json &model);
|
||||
bool Build(std::string &codeStr);
|
||||
std::list<std::shared_ptr<Connection> > GetNodeConnections(const std::string &nodeId);
|
||||
std::string GetNodeEntryLabel(const std::string &nodeId);
|
||||
void Load(std::shared_ptr<StoryProject> story);
|
||||
|
||||
std::shared_ptr<BaseNodeWidget> GetSelectedNode();
|
||||
|
||||
private:
|
||||
IStoryManager &m_story;
|
||||
|
||||
ed::EditorContext* m_context = nullptr;
|
||||
IStoryManager &m_manager;
|
||||
|
||||
bool m_loaded{false};
|
||||
|
||||
// key: Id
|
||||
ed::EditorContext* m_context = nullptr;
|
||||
|
||||
std::shared_ptr<StoryProject> m_story;
|
||||
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();
|
||||
|
|
@ -89,12 +86,12 @@ private:
|
|||
|
||||
template<class NodeType>
|
||||
struct Factory {
|
||||
static std::shared_ptr<BaseNodeWidget> create_func(const std::string &type, IStoryManager &proj) {
|
||||
return std::make_shared<NodeType>(type, proj);
|
||||
static std::shared_ptr<BaseNodeWidget> create_func(IStoryManager &manager, std::shared_ptr<BaseNode> base) {
|
||||
return std::make_shared<NodeType>(manager, base);
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<BaseNodeWidget> (*GenericCreator)(const std::string &type, IStoryManager &proj);
|
||||
typedef std::shared_ptr<BaseNodeWidget> (*GenericCreator)(IStoryManager &manager, std::shared_ptr<BaseNode> base);
|
||||
typedef std::map<std::string, GenericCreator> Registry;
|
||||
Registry m_registry;
|
||||
|
||||
|
|
@ -103,7 +100,7 @@ private:
|
|||
m_registry.insert(typename Registry::value_type(key, Factory<Derived>::create_func));
|
||||
}
|
||||
|
||||
std::shared_ptr<BaseNodeWidget> createNode(const std::string& key, IStoryManager &proj) {
|
||||
std::shared_ptr<BaseNodeWidget> CreateNodeWidget(const std::string& key, IStoryManager &manager, std::shared_ptr<BaseNode> base) {
|
||||
typename Registry::const_iterator i = m_registry.find(key);
|
||||
if (i == m_registry.end()) {
|
||||
throw std::invalid_argument(std::string(__PRETTY_FUNCTION__) +
|
||||
|
|
@ -111,16 +108,13 @@ private:
|
|||
}
|
||||
else
|
||||
{
|
||||
return i->second(key, proj);
|
||||
return i->second(manager, base);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
void CreateLink(std::shared_ptr<Connection> model, ed::PinId inId, ed::PinId outId);
|
||||
std::shared_ptr<Connection> LinkToModel(ed::PinId InputId, ed::PinId OutputId);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,29 @@
|
|||
#include "base_node.h"
|
||||
|
||||
#include "uuid.h"
|
||||
#include <iostream>
|
||||
|
||||
BaseNode::BaseNode(const std::string &type)
|
||||
{
|
||||
m_type = type;
|
||||
m_uuid = Uuid().String();
|
||||
|
||||
nlohmann::json obj{};
|
||||
m_internal_data = obj;
|
||||
}
|
||||
|
||||
BaseNode::~BaseNode()
|
||||
{
|
||||
std::cout << "Deleted base node" << std::endl;
|
||||
}
|
||||
|
||||
std::string BaseNode::GetEntryLabel(const std::string &id)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << ".nodeEntry" << std::setw(4) << std::setfill('0') << id;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
|
||||
void BaseNode::FromJson(const nlohmann::json &j)
|
||||
{
|
||||
try
|
||||
|
|
@ -16,6 +33,7 @@ void BaseNode::FromJson(const nlohmann::json &j)
|
|||
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)
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include "json.hpp"
|
||||
#include <random>
|
||||
#include <string>
|
||||
|
||||
#include "json.hpp"
|
||||
#include "i_story_project.h"
|
||||
|
||||
class BaseNode
|
||||
{
|
||||
|
|
@ -18,6 +18,12 @@ public:
|
|||
};
|
||||
|
||||
BaseNode(const std::string &type);
|
||||
virtual ~BaseNode();
|
||||
|
||||
static std::string GetEntryLabel(const std::string &id);
|
||||
|
||||
virtual std::string Build(IStoryProject &story, int nb_out_conns) = 0;
|
||||
virtual std::string GenerateConstants(IStoryProject &story, int nb_out_conns) = 0;
|
||||
|
||||
void SetPosition(float x, float y);
|
||||
|
||||
|
|
@ -50,6 +56,6 @@ private:
|
|||
std::string m_uuid;
|
||||
NodePosition m_pos;
|
||||
|
||||
nlohmann::json m_internal_data;
|
||||
nlohmann::json m_internal_data{{}};
|
||||
};
|
||||
|
||||
|
|
|
|||
14
story-editor/src/node_engine/i_script_node.h
Normal file
14
story-editor/src/node_engine/i_script_node.h
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "i_story_manager.h"
|
||||
#include "base_node.h"
|
||||
|
||||
class IScriptNode
|
||||
{
|
||||
public:
|
||||
virtual ~IScriptNode() {}
|
||||
|
||||
virtual std::string Build(IStoryManager &story, int nb_out_conns, BaseNode &base) = 0;
|
||||
virtual std::string GenerateConstants(IStoryManager &story, int nb_out_conns, BaseNode &base) = 0;
|
||||
};
|
||||
137
story-editor/src/node_engine/media_node.cpp
Normal file
137
story-editor/src/node_engine/media_node.cpp
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
#include "media_node.h"
|
||||
#include "story_project.h"
|
||||
#include "connection.h"
|
||||
|
||||
|
||||
static std::string ChoiceLabel(const std::string &id)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "mediaChoice" << std::setw(4) << std::setfill('0') << id;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
MediaNode::MediaNode(const std::string &type)
|
||||
: BaseNode(type)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
std::string MediaNode::GenerateConstants(IStoryProject &story, int nb_out_conns)
|
||||
{
|
||||
std::string s;
|
||||
|
||||
if (image.size() > 0)
|
||||
{
|
||||
s = StoryProject::FileToConstant(image, ".qoi"); // FIXME: Generate the extension setup in user option of output format
|
||||
}
|
||||
if (sound.size() > 0)
|
||||
{
|
||||
s += StoryProject::FileToConstant(sound, ".wav"); // FIXME: Generate the extension setup in user option of output format
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Generate choice table if needed (out ports > 1)
|
||||
std::stringstream ss;
|
||||
std::string label = ChoiceLabel(GetId());
|
||||
ss << "$" << label
|
||||
<< " DC32, "
|
||||
<< nb_out_conns << ", ";
|
||||
|
||||
std::list<std::shared_ptr<Connection>> conns = story.GetNodeConnections(GetId());
|
||||
int i = 0;
|
||||
for (auto & c : conns)
|
||||
{
|
||||
std::stringstream ssChoice;
|
||||
|
||||
// On va chercher le label d'entrée du noeud connecté à l'autre bout
|
||||
ss << BaseNode::GetEntryLabel(c->inNodeId);
|
||||
if (i < (nb_out_conns - 1))
|
||||
{
|
||||
ss << ", ";
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << "\n";
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
s += ss.str();
|
||||
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
std::string MediaNode::Build(IStoryProject &story, int nb_out_conns)
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
||||
ss << R"(; ---------------------------- )"
|
||||
<< GetTitle()
|
||||
<< " Type: "
|
||||
<< (nb_out_conns == 0 ? "End" : nb_out_conns == 1 ? "Transition" : "Choice")
|
||||
<< "\n";
|
||||
std::string image = StoryProject::RemoveFileExtension(image);
|
||||
std::string sound = StoryProject::RemoveFileExtension(sound);
|
||||
|
||||
// Le label de ce noeud est généré de la façon suivante :
|
||||
// "media" + Node ID + id du noeud parent. Si pas de noeud parent, alors rien
|
||||
ss << BaseNode::GetEntryLabel(GetId()) << ":\n";
|
||||
|
||||
if (image.size() > 0)
|
||||
{
|
||||
ss << "lcons r0, $" << image << "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << "lcons r0, 0\n";
|
||||
}
|
||||
|
||||
if (sound.size() > 0)
|
||||
{
|
||||
ss << "lcons r1, $" << sound << "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << "lcons r1, 0\n";
|
||||
}
|
||||
// Call the media executor (image, sound)
|
||||
ss << "syscall 1\n";
|
||||
|
||||
// Check output connections number
|
||||
// == 0: end node : generate halt
|
||||
// == 1: transition node : image + sound on demand, jump directly to the other node when OK
|
||||
// > 1 : choice node : call the node choice manager
|
||||
|
||||
if (nb_out_conns == 0) // End node
|
||||
{
|
||||
ss << "halt\n";
|
||||
}
|
||||
else if (nb_out_conns == 1) // it is a transition node
|
||||
{
|
||||
std::list<std::shared_ptr<Connection>> conns = story.GetNodeConnections(GetId());
|
||||
|
||||
|
||||
for (auto c : conns)
|
||||
{
|
||||
if (c->outNodeId == GetId())
|
||||
{
|
||||
// On place dans R0 le prochain noeud à exécuter en cas de OK
|
||||
ss << "lcons r0, "
|
||||
<< BaseNode::GetEntryLabel(c->inNodeId) << "\n"
|
||||
<< "ret\n";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else // Choice node
|
||||
{
|
||||
ss << "lcons r0, $" << ChoiceLabel(GetId()) << "\n"
|
||||
<< "jump .media ; no return possible, so a jump is enough";
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
20
story-editor/src/node_engine/media_node.h
Normal file
20
story-editor/src/node_engine/media_node.h
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "i_story_manager.h"
|
||||
#include "base_node.h"
|
||||
#include "i_script_node.h"
|
||||
#include "i_story_project.h"
|
||||
|
||||
struct MediaNode : public BaseNode
|
||||
{
|
||||
|
||||
MediaNode(const std::string &type);
|
||||
|
||||
virtual std::string Build(IStoryProject &story, int nb_out_conns) override;
|
||||
virtual std::string GenerateConstants(IStoryProject &story, int nb_out_conns) override;
|
||||
|
||||
std::string image;
|
||||
std::string sound;
|
||||
};
|
||||
|
||||
|
|
@ -25,7 +25,7 @@ void PropertiesWindow::Draw()
|
|||
if (m_selectedNode)
|
||||
{
|
||||
static char buf1[32] = ""; ImGui::InputText("Title", buf1, 32);
|
||||
ImGui::Text("Node ID: %s", m_selectedNode->GetId().data());
|
||||
ImGui::Text("Node ID: %s", m_selectedNode->Base()->GetId().data());
|
||||
m_selectedNode->DrawProperties();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ class WindowBase
|
|||
{
|
||||
public:
|
||||
WindowBase(const std::string &title);
|
||||
virtual ~WindowBase() {}
|
||||
|
||||
bool IsDisabled() const {
|
||||
return m_disabled;
|
||||
|
|
|
|||
Loading…
Reference in a new issue