mirror of
https://github.com/arabine/open-story-teller.git
synced 2025-12-06 17:09:06 +01:00
373 lines
9.5 KiB
C++
373 lines
9.5 KiB
C++
#include "media_node_model.h"
|
|
|
|
#include "story_node_data.h"
|
|
|
|
#include <QtNodes/NodeDelegateModelRegistry>
|
|
|
|
#include <QtCore/QDir>
|
|
#include <QtCore/QEvent>
|
|
#include <QtWidgets/QFileDialog>
|
|
#include <QMenu>
|
|
#include <qdebug.h>
|
|
|
|
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<int>::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<std::string>().c_str());
|
|
}
|
|
|
|
if (j.contains("sound")) {
|
|
QString fileName = j["sound"].get<std::string>().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>();
|
|
std::string sound = m_mediaData["sound"].get<std::string>();
|
|
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<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 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>());
|
|
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();
|
|
}
|
|
|
|
int MediaNodeModel::ComputeOutputConnections()
|
|
{
|
|
NodeId id = getNodeId();
|
|
std::unordered_set<ConnectionId> 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<StoryNodeData>(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<NodeData> MediaNodeModel::outData(PortIndex)
|
|
{
|
|
return m_nodeData;
|
|
}
|
|
|
|
void MediaNodeModel::setInData(std::shared_ptr<NodeData> nodeData, PortIndex const)
|
|
{
|
|
m_nodeData = nodeData;
|
|
|
|
if (m_nodeData) {
|
|
auto d = std::dynamic_pointer_cast<StoryNodeData>(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) };");
|
|
}
|