#include "media_node_model.h" #include "story_node_data.h" #include #include #include #include #include #include MediaNodeModel::MediaNodeModel(StoryGraphModel &model) : m_model(model) , m_widget(new StoryNodeWidgetBase()) { m_ui.setupUi(m_widget); m_ui.image->setText("Image will appear here"); m_ui.image->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter); QFont f = m_ui.image->font(); f.setBold(true); f.setItalic(true); m_ui.image->setFont(f); m_ui.image->setMinimumSize(320, 240); m_ui.image->installEventFilter(this); connect(m_ui.spinBox, QOverload::of(&QSpinBox::valueChanged), [&](int i) { bool addAction = false; if (m_ports < i) { addAction = true; } if (addAction) { m_model.addPort(getNodeId(), PortType::Out, m_ports - 1); } else { m_model.removePort(getNodeId(), PortType::Out, m_ports - 1); } m_ports = i; Q_EMIT m_model.nodeUpdated(getNodeId()); }); connect(m_ui.selectImageButton, &QPushButton::clicked, [&](bool enable) { emit m_model.sigChooseFile(getNodeId(), "image"); }); connect(m_ui.selectSoundButton, &QPushButton::clicked, [&](bool enable) { emit m_model.sigChooseFile(getNodeId(), "sound"); }); connect(m_ui.playSoundButton, &QPushButton::clicked, [&](bool enable) { m_model.PlaySound(m_soundFilePath); }); m_ui.playSoundButton->setEnabled(false); // default model m_mediaData = { {"image", ""}, {"sound", ""} }; m_mediaData.merge_patch(StoryNodeBase::ToJson()); QtNodes::NodeStyle _nodeStyle; QColor bgColor = QColor(94, 94, 94); _nodeStyle.GradientColor0 = bgColor; _nodeStyle.GradientColor1 = bgColor; _nodeStyle.GradientColor2 = bgColor; _nodeStyle.GradientColor3 = bgColor; _nodeStyle.NormalBoundaryColor = bgColor; _nodeStyle.FontColor = Qt::white; _nodeStyle.FontColorFaded = QColor(125, 125, 125); _nodeStyle.ShadowColor = QColor(20, 20, 20); _nodeStyle.ConnectionPointColor = QColor(125, 125, 125); _nodeStyle.FilledConnectionPointColor = QColor(206, 206, 206); _nodeStyle.SelectedBoundaryColor = QColor(20, 146, 202); _nodeStyle.Opacity = 1.0; _nodeStyle.PenWidth = 0; _nodeStyle.HoveredPenWidth = 2.0; _nodeStyle.ConnectionPointDiameter = 3.5; setNodeStyle(_nodeStyle); } QString MediaNodeModel::caption() const { return QString("Media Node " + QString::number(getNodeId())); } nlohmann::json MediaNodeModel::ToJson() const { // Always start with generic nlohmann::json j = StoryNodeBase::ToJson(); // Merge two objects j.merge_patch(m_mediaData); return j; } void MediaNodeModel::FromJson(nlohmann::json &j) { setInternalData(j); // Merge with SetInternalData ? } void MediaNodeModel::setImage(const QString &fileName) { QPixmap pix(m_model.BuildFullImagePath(fileName)); if (!pix.isNull()) { int w = m_ui.image->width(); int h = m_ui.image->height(); pix.scaled(w, h, Qt::KeepAspectRatio); m_ui.image->setPixmap(pix); m_ui.imageName->setText(fileName); } } void MediaNodeModel::setInternalData(const nlohmann::json &j) { if (j.contains("image")) { setImage(j["image"].get().c_str()); } if (j.contains("sound")) { QString fileName = j["sound"].get().c_str(); m_soundFilePath = m_model.BuildFullSoundPath(fileName); m_ui.soundName->setText(fileName); m_ui.playSoundButton->setEnabled(true); } // Merge new data into local object m_mediaData.merge_patch(j); } std::string MediaNodeModel::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); } if (sound.size() > 0) { s += StoryProject::FileToConstant(sound); } 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 MediaNodeModel::ChoiceLabel() const { std::stringstream ss; ss << "mediaChoice" << std::setw(4) << std::setfill('0') << getNodeId(); return ss.str(); } std::string MediaNodeModel::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(); } int MediaNodeModel::ComputeOutputConnections() { NodeId id = getNodeId(); std::unordered_set conns = m_model.allConnectionIds(id); int nb_out_conns = 0; for (auto & c : conns) { if (c.outNodeId == id) { nb_out_conns++; } } return nb_out_conns; } void MediaNodeModel::SetOutPortCount(int count) { m_ui.spinBox->setValue(count); } unsigned int MediaNodeModel::nPorts(PortType portType) const { unsigned int result = 1; switch (portType) { case PortType::In: result = 1; break; case PortType::Out: result = m_ports; default: break; } return result; } bool MediaNodeModel::eventFilter(QObject *object, QEvent *event) { if (object == m_ui.image) { int w = m_ui.image->width(); int h = m_ui.image->height(); if (event->type() == QEvent::Resize) { auto d = std::dynamic_pointer_cast(m_nodeData); if (d) { //_label->setPixmap(d->pixmap().scaled(w, h, Qt::KeepAspectRatio)); } } } return false; } NodeDataType MediaNodeModel::dataType(PortType const, PortIndex const) const { return StoryNodeData().type(); } std::shared_ptr MediaNodeModel::outData(PortIndex) { return m_nodeData; } void MediaNodeModel::setInData(std::shared_ptr nodeData, PortIndex const) { m_nodeData = nodeData; if (m_nodeData) { auto d = std::dynamic_pointer_cast(m_nodeData); int w = m_ui.image->width(); int h = m_ui.image->height(); // _label->setPixmap(d->pixmap().scaled(w, h, Qt::KeepAspectRatio)); } else { // _label->setPixmap(QPixmap()); } Q_EMIT dataUpdated(0); } std::string MediaNodeModel::EntryLabel() const { std::stringstream ss; ss << ".mediaEntry" << std::setw(4) << std::setfill('0') << getNodeId(); return ss.str(); } StoryNodeWidgetBase::StoryNodeWidgetBase() { // setWindowFlags(Qt::Window | Qt::FramelessWindowHint); // setAttribute(Qt::WA_NoSystemBackground); setAttribute(Qt::WA_TranslucentBackground); // setAttribute(Qt::WA_PaintOnScreen); // setAttribute(Qt::WA_TransparentForMouseEvents); setStyleSheet("QLabel { background-color: rgba(0,0,0,0) }; QWidget { background-color: rgba(0,0,0,0) };"); }