#include "media_node.h" namespace ed = ax::NodeEditor; #include "IconsMaterialDesignIcons.h" #include "story_project.h" MediaNode::MediaNode(const std::string &title, IStoryManager &proj) : BaseNode(title, proj) , m_story(proj) { Gui::LoadRawImage("fairy.png", m_image); // 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() { BaseNode::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" + std::to_string(GetId()); if (ImGui::ArrowButton(leftSingle.c_str(), ImGuiDir_Left)) { if (counter > 1) counter--; } ImGui::SameLine(0.0f, spacing); std::string rightSingle = "##right" + std::to_string(GetId()); if (ImGui::ArrowButton(rightSingle.c_str(), ImGuiDir_Right)) { counter++; } ImGui::PopButtonRepeat(); ImGui::SameLine(); ImGui::Text("%d", counter); SetOutputs(counter); DrawPins(); BaseNode::FrameEnd(); } /* "internal-data": { "image": "fairy.png", "sound": "la_fee_luminelle.mp3" }, */ void MediaNode::FromJson(const nlohmann::json &j) { SetImage(j["image"].get()); SetSound(j["sound"].get()); } void MediaNode::ToJson(nlohmann::json &j) { j["image"] = m_image.name; j["sound"] = m_soundName; } 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)); } void MediaNode::SetSound(const std::string &f) { m_soundName = f; m_soundPath = m_story.BuildFullAssetsPath(m_soundName); } 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> 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> 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(); }