diff --git a/story-editor/src/base_node.cpp b/story-editor/src/base_node.cpp index 5d6ce8b..b6817a0 100644 --- a/story-editor/src/base_node.cpp +++ b/story-editor/src/base_node.cpp @@ -47,7 +47,7 @@ void BaseNode::DeleteOutput() 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.y = y; @@ -93,3 +93,15 @@ void BaseNode::DrawPins() 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; +} diff --git a/story-editor/src/base_node.h b/story-editor/src/base_node.h index 7c2ae37..bc25b4d 100644 --- a/story-editor/src/base_node.h +++ b/story-editor/src/base_node.h @@ -97,8 +97,8 @@ class BaseNode public: struct NodePosition { - int x; - int y; + float x; + float y; }; BaseNode(const std::string &title, IStoryProject &proj); @@ -108,13 +108,17 @@ public: virtual void DrawProperties() = 0; - void SetPosition(int x, int y); + void SetPosition(float x, float y); void FrameStart(); void FrameEnd(); 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(); } void SetType(const std::string &type) @@ -134,9 +138,8 @@ public: void seTitle(const std::string &title) { m_title = title; } std::string getTitle() const { return m_title; } - virtual void FromJson(nlohmann::json &) { - // default implementation does nothing - } + virtual void FromJson(const nlohmann::json &) = 0; + virtual void ToJson(nlohmann::json &) = 0; virtual nlohmann::json ToJson() const { nlohmann::json j; @@ -179,6 +182,36 @@ public: 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 AddOutputs(int num = 1); diff --git a/story-editor/src/main_window.cpp b/story-editor/src/main_window.cpp index 16a028b..b427933 100644 --- a/story-editor/src/main_window.cpp +++ b/story-editor/src/main_window.cpp @@ -78,6 +78,11 @@ void MainWindow::DrawMainMenuBar() showOpenProject = true; } + if (ImGui::MenuItem("Save project")) + { + SaveProject(); + } + if (ImGui::MenuItem("Close project")) { CloseProject(); @@ -138,9 +143,10 @@ void MainWindow::DrawMainMenuBar() if (ImGui::BeginPopupModal("AboutPopup", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::Text("Story Editor V1"); + ImGui::Text("Story Editor V2"); ImGui::Separator(); 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("CPU cores: %d", SDL_GetCPUCount()); // ImGui::Text("RAM: %.2f GB", SDL_GetSystemRAM() / 1024.0f); @@ -550,7 +556,8 @@ void MainWindow::NewProjectPopup() void MainWindow::SaveProject() { - nlohmann::json model; // = m_model.Save(); + nlohmann::json model; + m_nodeEditorWindow.Save(model); m_project.Save(model, m_resources); } diff --git a/story-editor/src/media_node.cpp b/story-editor/src/media_node.cpp index 9938728..d1eab5b 100644 --- a/story-editor/src/media_node.cpp +++ b/story-editor/src/media_node.cpp @@ -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(); @@ -111,6 +111,12 @@ void MediaNode::FromJson(nlohmann::json &j) 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() { ImGui::AlignTextToFramePadding(); diff --git a/story-editor/src/media_node.h b/story-editor/src/media_node.h index 5e85554..6c2372f 100644 --- a/story-editor/src/media_node.h +++ b/story-editor/src/media_node.h @@ -18,7 +18,8 @@ public: 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; diff --git a/story-editor/src/node_editor_window.cpp b/story-editor/src/node_editor_window.cpp index ccc30fe..5b295b1 100644 --- a/story-editor/src/node_editor_window.cpp +++ b/story-editor/src/node_editor_window.cpp @@ -54,7 +54,7 @@ void NodeEditorWindow::LoadNode(const nlohmann::json &nodeJson) n->SetId(restoredNodeId); nlohmann::json posJson = nodeJson["position"]; n->SetOutputs(nodeJson["outPortCount"].get()); - n->SetPosition(posJson["x"].get(), posJson["y"].get()); + n->SetPosition(posJson["x"].get(), posJson["y"].get()); n->FromJson(internalDataJson); 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 sound = m_mediaData["sound"].get(); + 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 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 sound = StoryProject::RemoveFileExtension(m_mediaData["sound"].get()); + + // 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 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 NodeEditorWindow::GetSelectedNode() { std::shared_ptr selected; diff --git a/story-editor/src/node_editor_window.h b/story-editor/src/node_editor_window.h index d48db36..7a3a5c1 100644 --- a/story-editor/src/node_editor_window.h +++ b/story-editor/src/node_editor_window.h @@ -46,8 +46,10 @@ public: void Initialize(); void Clear(); void Load(const nlohmann::json &model); + void Save(nlohmann::json &model); std::shared_ptr GetSelectedNode(); + std::string GenerateConstants(); private: IStoryProject &m_project; @@ -102,5 +104,7 @@ private: void LoadNode(const nlohmann::json &nodeJson); ed::PinId GetInputPin(unsigned long modelNodeId, int pinIndex); ed::PinId GetOutputPin(unsigned long modelNodeId, int pinIndex); + std::string ChoiceLabel() const; + std::string EntryLabel() const; };