save project (WIP), bugs in conection ids and missing image resources

This commit is contained in:
Anthony Rabine 2023-12-17 15:39:40 +01:00
parent 79445e1505
commit 308586d4b8
7 changed files with 273 additions and 12 deletions

View file

@ -47,7 +47,7 @@ void BaseNode::DeleteOutput()
m_node->Outputs.pop_back(); m_node->Outputs.pop_back();
} }
void BaseNode::SetPosition(int x, int y) void BaseNode::SetPosition(float x, float y)
{ {
m_pos.x = x; m_pos.x = x;
m_pos.y = y; m_pos.y = y;
@ -93,3 +93,15 @@ void BaseNode::DrawPins()
ed::EndPin(); ed::EndPin();
} }
} }
float BaseNode::GetX() const
{
auto pos = GetNodePosition(m_node->ID);
return pos.x;
}
float BaseNode::GetY() const
{
auto pos = GetNodePosition(m_node->ID);
return pos.y;
}

View file

@ -97,8 +97,8 @@ class BaseNode
public: public:
struct NodePosition struct NodePosition
{ {
int x; float x;
int y; float y;
}; };
BaseNode(const std::string &title, IStoryProject &proj); BaseNode(const std::string &title, IStoryProject &proj);
@ -108,13 +108,17 @@ public:
virtual void DrawProperties() = 0; virtual void DrawProperties() = 0;
void SetPosition(int x, int y); void SetPosition(float x, float y);
void FrameStart(); void FrameStart();
void FrameEnd(); void FrameEnd();
void DrawPins(); void DrawPins();
float GetX() const;
float GetY() const;
uint32_t Inputs() const { return m_node->Inputs.size(); }
uint32_t Outputs() const { return m_node->Outputs.size(); } uint32_t Outputs() const { return m_node->Outputs.size(); }
void SetType(const std::string &type) void SetType(const std::string &type)
@ -134,9 +138,8 @@ public:
void seTitle(const std::string &title) { m_title = title; } void seTitle(const std::string &title) { m_title = title; }
std::string getTitle() const { return m_title; } std::string getTitle() const { return m_title; }
virtual void FromJson(nlohmann::json &) { virtual void FromJson(const nlohmann::json &) = 0;
// default implementation does nothing virtual void ToJson(nlohmann::json &) = 0;
}
virtual nlohmann::json ToJson() const { virtual nlohmann::json ToJson() const {
nlohmann::json j; nlohmann::json j;
@ -179,6 +182,36 @@ public:
return id; return id;
} }
bool HasInputPinId(ed::PinId &pinId, int &atIndex) const
{
bool found = false;
atIndex = 0;
for (auto &i : m_node->Inputs)
{
if (i.ID == pinId)
{
found = true;
}
atIndex++;
}
return found;
}
bool HasOnputPinId(ed::PinId &pinId, int &atIndex) const
{
bool found = false;
atIndex = 0;
for (auto &i : m_node->Outputs)
{
if (i.ID == pinId)
{
found = true;
}
atIndex++;
}
return found;
}
void AddInput(); void AddInput();
void AddOutputs(int num = 1); void AddOutputs(int num = 1);

View file

@ -78,6 +78,11 @@ void MainWindow::DrawMainMenuBar()
showOpenProject = true; showOpenProject = true;
} }
if (ImGui::MenuItem("Save project"))
{
SaveProject();
}
if (ImGui::MenuItem("Close project")) if (ImGui::MenuItem("Close project"))
{ {
CloseProject(); CloseProject();
@ -138,9 +143,10 @@ void MainWindow::DrawMainMenuBar()
if (ImGui::BeginPopupModal("AboutPopup", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) if (ImGui::BeginPopupModal("AboutPopup", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
{ {
ImGui::Text("Story Editor V1"); ImGui::Text("Story Editor V2");
ImGui::Separator(); ImGui::Separator();
ImGui::TextColored(ImVec4(1.0f, 0.0f, 1.0f, 1.0f), "Platform"); ImGui::TextColored(ImVec4(1.0f, 0.0f, 1.0f, 1.0f), "Platform");
ImGui::Text("http://www.openstoryteller.org");
// ImGui::Text("%s", SDL_GetPlatform()); // ImGui::Text("%s", SDL_GetPlatform());
// ImGui::Text("CPU cores: %d", SDL_GetCPUCount()); // ImGui::Text("CPU cores: %d", SDL_GetCPUCount());
// ImGui::Text("RAM: %.2f GB", SDL_GetSystemRAM() / 1024.0f); // ImGui::Text("RAM: %.2f GB", SDL_GetSystemRAM() / 1024.0f);
@ -550,7 +556,8 @@ void MainWindow::NewProjectPopup()
void MainWindow::SaveProject() void MainWindow::SaveProject()
{ {
nlohmann::json model; // = m_model.Save(); nlohmann::json model;
m_nodeEditorWindow.Save(model);
m_project.Save(model, m_resources); m_project.Save(model, m_resources);
} }

View file

@ -101,7 +101,7 @@ void MediaNode::Draw()
}, },
*/ */
void MediaNode::FromJson(nlohmann::json &j) void MediaNode::FromJson(const nlohmann::json &j)
{ {
m_image.name = j["image"].get<std::string>(); m_image.name = j["image"].get<std::string>();
@ -111,6 +111,12 @@ void MediaNode::FromJson(nlohmann::json &j)
m_soundPath = m_project.BuildFullAssetsPath(m_soundName); m_soundPath = m_project.BuildFullAssetsPath(m_soundName);
} }
void MediaNode::ToJson(nlohmann::json &j)
{
j["image"] = m_image.name;
j["sound"] = m_soundName;
}
void MediaNode::DrawProperties() void MediaNode::DrawProperties()
{ {
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();

View file

@ -18,7 +18,8 @@ public:
void Draw() override; void Draw() override;
virtual void FromJson(nlohmann::json &j) override; virtual void FromJson(const nlohmann::json &j) override;
virtual void ToJson(nlohmann::json &j) override;
virtual void DrawProperties() override; virtual void DrawProperties() override;

View file

@ -54,7 +54,7 @@ void NodeEditorWindow::LoadNode(const nlohmann::json &nodeJson)
n->SetId(restoredNodeId); n->SetId(restoredNodeId);
nlohmann::json posJson = nodeJson["position"]; nlohmann::json posJson = nodeJson["position"];
n->SetOutputs(nodeJson["outPortCount"].get<int>()); n->SetOutputs(nodeJson["outPortCount"].get<int>());
n->SetPosition(posJson["x"].get<int>(), posJson["y"].get<int>()); n->SetPosition(posJson["x"].get<float>(), posJson["y"].get<float>());
n->FromJson(internalDataJson); n->FromJson(internalDataJson);
m_nodes[n->GetInternalId()] = n; m_nodes[n->GetInternalId()] = n;
@ -146,6 +146,204 @@ void NodeEditorWindow::Load(const nlohmann::json &model)
} }
} }
void NodeEditorWindow::Save(nlohmann::json &model)
{
ed::SetCurrentEditor(m_context);
// Save nodes
nlohmann::json nodes = nlohmann::json::array();
for (const auto & n : m_nodes)
{
nlohmann::json node;
node["id"] = n.second->GetId();
node["type"] = n.second->GetType();
node["outPortCount"] = n.second->Outputs();
node["inPortCount"] = n.second->Inputs();
nlohmann::json position;
position["x"] = n.second->GetX();
position["y"] = n.second->GetY();
nlohmann::json internalData;
n.second->ToJson(internalData);
node["position"] = position;
node["internal-data"] = internalData;
}
model["nodes"] = nodes;
// Save links
nlohmann::json connections = nlohmann::json::array();
for (const auto& linkInfo : m_links)
{
nlohmann::json c;
int index;
for (const auto & n : m_nodes)
{
if (n.second->HasOnputPinId(linkInfo->OutputId, index))
{
c["outNodeId"] = n.second->GetId();
c["outPortIndex"] = index;
}
if (n.second->HasInputPinId(linkInfo->InputId, index))
{
c["inNodeId"] = n.second->GetId();
c["inPortIndex"] = index;
}
}
connections.push_back(c);
ed::Link(linkInfo->Id, linkInfo->OutputId, linkInfo->InputId);
}
model["connections"] = connections;
ed::SetCurrentEditor(nullptr);
}
/*
std::string NodeEditorWindow::ChoiceLabel() const
{
std::stringstream ss;
ss << "mediaChoice" << std::setw(4) << std::setfill('0') << GetId();
return ss.str();
}
std::string NodeEditorWindow::EntryLabel() const
{
std::stringstream ss;
ss << ".mediaEntry" << std::setw(4) << std::setfill('0') << getNodeId();
return ss.str();
}
std::string NodeEditorWindow::GenerateConstants()
{
std::string s;
std::string image = m_mediaData["image"].get<std::string>();
std::string sound = m_mediaData["sound"].get<std::string>();
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
}
int nb_out_conns = ComputeOutputConnections();
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::unordered_set<ConnectionId> conns = m_model.allConnectionIds(getNodeId());
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_model.GetNodeEntryLabel(c.inNodeId);
if (i < (nb_out_conns - 1))
{
ss << ", ";
}
else
{
ss << "\n";
}
i++;
}
s += ss.str();
}
return s;
}
std::string NodeEditorWindow::Build()
{
std::stringstream ss;
int nb_out_conns = ComputeOutputConnections();
ss << R"(; ---------------------------- )"
<< GetNodeTitle()
<< " Type: "
<< (nb_out_conns == 0 ? "End" : nb_out_conns == 1 ? "Transition" : "Choice")
<< "\n";
std::string image = StoryProject::RemoveFileExtension(m_mediaData["image"].get<std::string>());
std::string sound = StoryProject::RemoveFileExtension(m_mediaData["sound"].get<std::string>());
// 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 << EntryLabel() << ":\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) // Transition node
{
std::unordered_set<ConnectionId> conns = m_model.allConnectionIds(getNodeId());
for (auto c : conns)
{
if (c.outNodeId == getNodeId())
{
// On place dans R0 le prochain noeud à exécuter en cas de OK
ss << "lcons r0, "
<< m_model.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();
}
*/
std::shared_ptr<BaseNode> NodeEditorWindow::GetSelectedNode() std::shared_ptr<BaseNode> NodeEditorWindow::GetSelectedNode()
{ {
std::shared_ptr<BaseNode> selected; std::shared_ptr<BaseNode> selected;

View file

@ -46,8 +46,10 @@ public:
void Initialize(); void Initialize();
void Clear(); void Clear();
void Load(const nlohmann::json &model); void Load(const nlohmann::json &model);
void Save(nlohmann::json &model);
std::shared_ptr<BaseNode> GetSelectedNode(); std::shared_ptr<BaseNode> GetSelectedNode();
std::string GenerateConstants();
private: private:
IStoryProject &m_project; IStoryProject &m_project;
@ -102,5 +104,7 @@ private:
void LoadNode(const nlohmann::json &nodeJson); void LoadNode(const nlohmann::json &nodeJson);
ed::PinId GetInputPin(unsigned long modelNodeId, int pinIndex); ed::PinId GetInputPin(unsigned long modelNodeId, int pinIndex);
ed::PinId GetOutputPin(unsigned long modelNodeId, int pinIndex); ed::PinId GetOutputPin(unsigned long modelNodeId, int pinIndex);
std::string ChoiceLabel() const;
std::string EntryLabel() const;
}; };