mirror of
https://github.com/arabine/open-story-teller.git
synced 2025-12-06 17:09:06 +01:00
save project (WIP), bugs in conection ids and missing image resources
This commit is contained in:
parent
79445e1505
commit
308586d4b8
7 changed files with 273 additions and 12 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>();
|
||||
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ void NodeEditorWindow::LoadNode(const nlohmann::json &nodeJson)
|
|||
n->SetId(restoredNodeId);
|
||||
nlohmann::json posJson = nodeJson["position"];
|
||||
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);
|
||||
|
||||
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> selected;
|
||||
|
|
|
|||
|
|
@ -46,8 +46,10 @@ public:
|
|||
void Initialize();
|
||||
void Clear();
|
||||
void Load(const nlohmann::json &model);
|
||||
void Save(nlohmann::json &model);
|
||||
|
||||
std::shared_ptr<BaseNode> 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;
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue