implemented external xml/shader loading for transitions as well as regular nodes

Transitions are nodes but have a few parameters that are programmatic and always
required. This lets people write external code for transitions as well.
This commit is contained in:
itsmattkc
2019-12-17 15:44:13 +11:00
parent a0b6bb842d
commit 6a4dcc673a
9 changed files with 477 additions and 259 deletions
+2
View File
@@ -37,6 +37,8 @@ set(OLIVE_SOURCES
node/inputarray.cpp
node/keyframe.h
node/keyframe.cpp
node/metareader.h
node/metareader.cpp
node/node.h
node/node.cpp
node/output.h
+2
View File
@@ -19,6 +19,8 @@ add_subdirectory(diptoblack)
set(OLIVE_SOURCES
${OLIVE_SOURCES}
node/block/transition/externaltransition.h
node/block/transition/externaltransition.cpp
node/block/transition/transition.h
node/block/transition/transition.cpp
PARENT_SCOPE
@@ -0,0 +1,64 @@
#include "externaltransition.h"
ExternalTransition::ExternalTransition(const QString &xml_meta_filename) :
meta_(xml_meta_filename)
{
foreach (NodeInput* input, meta_.inputs()) {
AddInput(input);
}
}
Node *ExternalTransition::copy() const
{
return new ExternalTransition(meta_.filename());
}
QString ExternalTransition::Name() const
{
return meta_.Name();
}
QString ExternalTransition::id() const
{
return meta_.id();
}
QString ExternalTransition::Category() const
{
return meta_.Category();
}
QString ExternalTransition::Description() const
{
return meta_.Description();
}
void ExternalTransition::Retranslate()
{
meta_.Retranslate();
}
bool ExternalTransition::IsAccelerated() const
{
return true;
}
QString ExternalTransition::AcceleratedCodeVertex() const
{
return meta_.vert_code();
}
QString ExternalTransition::AcceleratedCodeFragment() const
{
return meta_.frag_code();
}
int ExternalTransition::AcceleratedCodeIterations() const
{
return meta_.iterations();
}
NodeInput *ExternalTransition::AcceleratedCodeIterativeInput() const
{
return meta_.iteration_input();
}
@@ -0,0 +1,32 @@
#ifndef EXTERNALTRANSITION_H
#define EXTERNALTRANSITION_H
#include "transition.h"
#include "node/metareader.h"
class ExternalTransition : public TransitionBlock
{
public:
ExternalTransition(const QString& xml_meta_filename);
virtual Node* copy() const override;
virtual QString Name() const override;
virtual QString id() const override;
virtual QString Category() const override;
virtual QString Description() const override;
virtual void Retranslate() override;
virtual bool IsAccelerated() const override;
virtual QString AcceleratedCodeVertex() const override;
virtual QString AcceleratedCodeFragment() const override;
virtual int AcceleratedCodeIterations() const override;
virtual NodeInput* AcceleratedCodeIterativeInput() const override;
private:
NodeMetaReader meta_;
};
#endif // EXTERNALTRANSITION_H
+13 -235
View File
@@ -2,66 +2,42 @@
#include <QFile>
#include "config/config.h"
ExternalNode::ExternalNode(const QString &xml_meta_filename) :
xml_filename_(xml_meta_filename),
iterations_(1),
iteration_input_(nullptr)
meta_(xml_meta_filename)
{
QFile metadata_file(xml_filename_);
if (metadata_file.open(QFile::ReadOnly)) {
QXmlStreamReader reader(&metadata_file);
while (!reader.atEnd()) {
reader.readNext();
if (reader.isStartElement() && reader.name() == "effect") {
XMLReadEffect(&reader);
}
}
metadata_file.close();
} else {
qWarning() << "Failed to load node metadata file" << xml_filename_;
foreach (NodeInput* input, meta_.inputs()) {
AddInput(input);
}
}
Node *ExternalNode::copy() const
{
return new ExternalNode(xml_filename_);
return new ExternalNode(meta_.filename());
}
QString ExternalNode::Name() const
{
return GetStringForCurrentLanguage(&names_);
return meta_.Name();
}
QString ExternalNode::id() const
{
return id_;
return meta_.id();
}
QString ExternalNode::Category() const
{
return GetStringForCurrentLanguage(&categories_);
return meta_.Category();
}
QString ExternalNode::Description() const
{
return GetStringForCurrentLanguage(&descriptions_);
return meta_.Description();
}
void ExternalNode::Retranslate()
{
QMap<QString, QMap<QString, QString> >::const_iterator iterator;
// Iterate through parameter language tables that we have
for (iterator=param_names_.begin();iterator!=param_names_.end();iterator++) {
NodeInput* this_input = static_cast<NodeInput*>(GetParameterWithID(iterator.key()));
this_input->set_name(GetStringForCurrentLanguage(&iterator.value()));
}
meta_.Retranslate();
}
bool ExternalNode::IsAccelerated() const
@@ -71,218 +47,20 @@ bool ExternalNode::IsAccelerated() const
QString ExternalNode::AcceleratedCodeVertex() const
{
return vert_code_;
return meta_.vert_code();
}
QString ExternalNode::AcceleratedCodeFragment() const
{
return frag_code_;
return meta_.frag_code();
}
int ExternalNode::AcceleratedCodeIterations() const
{
return iterations_;
return meta_.iterations();
}
NodeInput *ExternalNode::AcceleratedCodeIterativeInput() const
{
return iteration_input_;
}
void ExternalNode::XMLReadLanguageString(QXmlStreamReader* reader, QMap<QString, QString>* map)
{
QString lang;
// Traverse through name attributes for its language
QXmlStreamAttributes attrs = reader->attributes();
foreach (const QXmlStreamAttribute& attr, attrs) {
if (attr.name() == "lang") {
lang = attr.value().toString();
// We don't recognize any other "name" attributes at this time
break;
}
}
// Insert name with language into map
map->insert(lang, reader->readElementText().trimmed());
}
void ExternalNode::XMLReadEffect(QXmlStreamReader* reader)
{
// Traverse through effect attributes for an ID
QXmlStreamAttributes attrs = reader->attributes();
foreach (const QXmlStreamAttribute& attr, attrs) {
if (attr.name() == "id") {
id_ = attr.value().toString();
// We don't recognize any other "effect" attributes at this time
break;
}
}
if (id_.isEmpty()) {
qWarning() << "Effect metadata" << xml_filename_ << "has no ID";
return;
}
// Continue reading for other metadata
while (!reader->atEnd() && !(reader->isEndElement() && reader->name() == "effect")) {
reader->readNext();
if (reader->isStartElement()) {
if (reader->name() == "name") {
// Pick up name
XMLReadLanguageString(reader, &names_);
} else if (reader->name() == "category") {
// Pick up category
XMLReadLanguageString(reader, &categories_);
} else if (reader->name() == "description") {
// Pick up description
XMLReadLanguageString(reader, &descriptions_);
} else if (reader->name() == "iterations") {
// Pick up iterations
XMLReadIterations(reader);
} else if (reader->name() == "fragment") {
// Pick up fragment shader code
XMLReadShader(reader, frag_code_);
} else if (reader->name() == "vertex") {
// Pick up vertex shader code
XMLReadShader(reader, vert_code_);
} else if (reader->name() == "param") {
// Pick up a parameter
XMLReadParam(reader);
}
}
}
}
void ExternalNode::XMLReadIterations(QXmlStreamReader* reader)
{
int iteration_pickup = reader->readElementText().toInt();
if (iterations_ > 0) {
iterations_ = iteration_pickup;
} else {
// If the iteration value is invalid, don't set it, print an error insteead
qWarning() << "Invalid iteration number in" << xml_filename_ << "- setting to default (1)";
}
}
void ExternalNode::XMLReadParam(QXmlStreamReader *reader)
{
QString param_id;
NodeParam::DataType param_type = NodeParam::kAny;
bool is_iterative = false;
// Traverse through parameter attributes for an ID
QXmlStreamAttributes attrs = reader->attributes();
foreach (const QXmlStreamAttribute& attr, attrs) {
if (attr.name() == "id") {
param_id = attr.value().toString();
} else if (attr.name() == "type") {
param_type = NodeParam::StringToDataType(attr.value().toString());
} else if (attr.name() == "iterative_input") {
is_iterative = true;
}
}
if (param_id.isEmpty()) {
qWarning() << "Effect metadata" << xml_filename_ << "contains a parameter with no ID - parameter was not added";
return;
}
NodeInput* input = new NodeInput(param_id);
input->set_data_type(param_type);
if (is_iterative) {
iteration_input_ = input;
}
// Traverse through param contents for more data
while (!reader->atEnd() && !(reader->isEndElement() && reader->name() == "param")) {
reader->readNext();
if (reader->isStartElement()) {
// NOTE: readElementText() returns a string, but for number types (which min and max apply to), QVariant will
// convert them automatically
if (reader->name() == "name") {
// See if we've already made a table for this param before
if (!param_names_.contains(param_id)) {
param_names_.insert(param_id, QMap<QString, QString>());
}
// Insert language into table
XMLReadLanguageString(reader, &param_names_[param_id]);
} else if (reader->name() == "default") {
input->set_value_at_time(0, reader->readElementText());
} else if (reader->name() == "min") {
input->set_minimum(reader->readElementText());
} else if (reader->name() == "max") {
input->set_maximum(reader->readElementText());
}
}
}
AddInput(input);
}
void ExternalNode::XMLReadShader(QXmlStreamReader *reader, QString &destination)
{
QString code_url;
// Traverse through parameter attributes for an ID
QXmlStreamAttributes attrs = reader->attributes();
foreach (const QXmlStreamAttribute& attr, attrs) {
if (attr.name() == "url") {
code_url = attr.value().toString();
// We don't recognize any other "shader" attributes at this time
break;
}
}
// Add code in file from URL
if (!code_url.isEmpty()) {
destination.append(ReadFileAsString(code_url));
}
// Add any code that's inline in the XML
QString element_text = reader->readElementText().trimmed();
if (!element_text.isEmpty()) {
destination.append(element_text);
}
}
QString ExternalNode::GetStringForCurrentLanguage(const QMap<QString, QString> *language_map)
{
if (language_map->isEmpty()) {
// There are no entries for this map, this must be an empty string
return QString();
}
// Get current language config
QString language = Config::Current()["Language"].toString();
// See if our map has an exact language match
QString str_for_lang = language_map->value(language);
if (!str_for_lang.isEmpty()) {
return str_for_lang;
}
// If not, try to find a match with the same language but not the same derivation
QString base_lang = language.split('_').first();
QList<QString> available_languages = language_map->keys();
foreach (const QString& l, available_languages) {
if (l.startsWith(base_lang)) {
// This is the same language, so we can return this
return language_map->value(l);
}
}
// We couldn't find an exact or close match, just return the first in the list
// (assume a string in the wrong language is better than no string at all)
return language_map->first();
return meta_.iteration_input();
}
+2 -22
View File
@@ -4,6 +4,7 @@
#include <QXmlStreamReader>
#include "node.h"
#include "metareader.h"
/**
* @brief A node generated from an external XML metadata file
@@ -29,28 +30,7 @@ public:
virtual NodeInput* AcceleratedCodeIterativeInput() const override;
private:
void XMLReadLanguageString(QXmlStreamReader* reader, QMap<QString, QString>* map);
void XMLReadEffect(QXmlStreamReader *reader);
void XMLReadIterations(QXmlStreamReader* reader);
void XMLReadParam(QXmlStreamReader* reader);
void XMLReadShader(QXmlStreamReader* reader, QString& destination);
static QString GetStringForCurrentLanguage(const QMap<QString, QString> *language_map);
QString xml_filename_;
QMap<QString, QString> names_;
QMap<QString, QString> descriptions_;
QMap<QString, QString> categories_;
QMap<QString, QMap<QString, QString> > param_names_;
QString id_;
QString frag_code_;
QString vert_code_;
int iterations_;
NodeInput* iteration_input_;
NodeMetaReader meta_;
};
#endif // EXTERNALNODE_H
+299
View File
@@ -0,0 +1,299 @@
#include "metareader.h"
#include <QFile>
#include "config/config.h"
#include "node.h"
NodeMetaReader::NodeMetaReader(const QString &xml_meta_filename) :
xml_filename_(xml_meta_filename),
iterations_(1),
iteration_input_(nullptr)
{
QFile metadata_file(xml_filename_);
if (metadata_file.open(QFile::ReadOnly)) {
QXmlStreamReader reader(&metadata_file);
while (!reader.atEnd()) {
reader.readNext();
if (reader.isStartElement() && reader.name() == "effect") {
XMLReadEffect(&reader);
}
}
metadata_file.close();
} else {
qWarning() << "Failed to load node metadata file" << xml_filename_;
}
}
QString NodeMetaReader::Name() const
{
return GetStringForCurrentLanguage(&names_);
}
const QString &NodeMetaReader::id() const
{
return id_;
}
QString NodeMetaReader::Category() const
{
return GetStringForCurrentLanguage(&categories_);
}
QString NodeMetaReader::Description() const
{
return GetStringForCurrentLanguage(&descriptions_);
}
const QString &NodeMetaReader::filename() const
{
return xml_filename_;
}
const QString &NodeMetaReader::frag_code() const
{
return frag_code_;
}
const QString &NodeMetaReader::vert_code() const
{
return vert_code_;
}
const int &NodeMetaReader::iterations() const
{
return iterations_;
}
NodeInput *NodeMetaReader::iteration_input() const
{
return iteration_input_;
}
const QList<NodeInput *> &NodeMetaReader::inputs() const
{
return inputs_;
}
void NodeMetaReader::Retranslate()
{
QMap<QString, QMap<QString, QString> >::const_iterator iterator;
// Iterate through parameter language tables that we have
for (iterator=param_names_.begin();iterator!=param_names_.end();iterator++) {
NodeInput* this_input = GetInputWithID(iterator.key());
this_input->set_name(GetStringForCurrentLanguage(&iterator.value()));
}
}
void NodeMetaReader::XMLReadLanguageString(QXmlStreamReader* reader, QMap<QString, QString>* map)
{
QString lang;
// Traverse through name attributes for its language
QXmlStreamAttributes attrs = reader->attributes();
foreach (const QXmlStreamAttribute& attr, attrs) {
if (attr.name() == "lang") {
lang = attr.value().toString();
// We don't recognize any other "name" attributes at this time
break;
}
}
// Insert name with language into map
map->insert(lang, reader->readElementText().trimmed());
}
void NodeMetaReader::XMLReadEffect(QXmlStreamReader* reader)
{
// Traverse through effect attributes for an ID
QXmlStreamAttributes attrs = reader->attributes();
foreach (const QXmlStreamAttribute& attr, attrs) {
if (attr.name() == "id") {
id_ = attr.value().toString();
// We don't recognize any other "effect" attributes at this time
break;
}
}
if (id_.isEmpty()) {
qWarning() << "Effect metadata" << xml_filename_ << "has no ID";
return;
}
// Continue reading for other metadata
while (!reader->atEnd() && !(reader->isEndElement() && reader->name() == "effect")) {
reader->readNext();
if (reader->isStartElement()) {
if (reader->name() == "name") {
// Pick up name
XMLReadLanguageString(reader, &names_);
} else if (reader->name() == "category") {
// Pick up category
XMLReadLanguageString(reader, &categories_);
} else if (reader->name() == "description") {
// Pick up description
XMLReadLanguageString(reader, &descriptions_);
} else if (reader->name() == "iterations") {
// Pick up iterations
XMLReadIterations(reader);
} else if (reader->name() == "fragment") {
// Pick up fragment shader code
XMLReadShader(reader, frag_code_);
} else if (reader->name() == "vertex") {
// Pick up vertex shader code
XMLReadShader(reader, vert_code_);
} else if (reader->name() == "param") {
// Pick up a parameter
XMLReadParam(reader);
}
}
}
}
void NodeMetaReader::XMLReadIterations(QXmlStreamReader* reader)
{
int iteration_pickup = reader->readElementText().toInt();
if (iterations_ > 0) {
iterations_ = iteration_pickup;
} else {
// If the iteration value is invalid, don't set it, print an error insteead
qWarning() << "Invalid iteration number in" << xml_filename_ << "- setting to default (1)";
}
}
void NodeMetaReader::XMLReadParam(QXmlStreamReader *reader)
{
QString param_id;
NodeParam::DataType param_type = NodeParam::kAny;
bool is_iterative = false;
// Traverse through parameter attributes for an ID
QXmlStreamAttributes attrs = reader->attributes();
foreach (const QXmlStreamAttribute& attr, attrs) {
if (attr.name() == "id") {
param_id = attr.value().toString();
} else if (attr.name() == "type") {
param_type = NodeParam::StringToDataType(attr.value().toString());
} else if (attr.name() == "iterative_input") {
is_iterative = true;
}
}
if (param_id.isEmpty()) {
qWarning() << "Effect metadata" << xml_filename_ << "contains a parameter with no ID - parameter was not added";
return;
}
NodeInput* input = new NodeInput(param_id);
input->set_data_type(param_type);
if (is_iterative) {
iteration_input_ = input;
}
// Traverse through param contents for more data
while (!reader->atEnd() && !(reader->isEndElement() && reader->name() == "param")) {
reader->readNext();
if (reader->isStartElement()) {
// NOTE: readElementText() returns a string, but for number types (which min and max apply to), QVariant will
// convert them automatically
if (reader->name() == "name") {
// See if we've already made a table for this param before
if (!param_names_.contains(param_id)) {
param_names_.insert(param_id, QMap<QString, QString>());
}
// Insert language into table
XMLReadLanguageString(reader, &param_names_[param_id]);
} else if (reader->name() == "default") {
input->set_value_at_time(0, reader->readElementText());
} else if (reader->name() == "min") {
input->set_minimum(reader->readElementText());
} else if (reader->name() == "max") {
input->set_maximum(reader->readElementText());
}
}
}
inputs_.append(input);
}
void NodeMetaReader::XMLReadShader(QXmlStreamReader *reader, QString &destination)
{
QString code_url;
// Traverse through parameter attributes for an ID
QXmlStreamAttributes attrs = reader->attributes();
foreach (const QXmlStreamAttribute& attr, attrs) {
if (attr.name() == "url") {
code_url = attr.value().toString();
// We don't recognize any other "shader" attributes at this time
break;
}
}
// Add code in file from URL
if (!code_url.isEmpty()) {
destination.append(Node::ReadFileAsString(code_url));
}
// Add any code that's inline in the XML
QString element_text = reader->readElementText().trimmed();
if (!element_text.isEmpty()) {
destination.append(element_text);
}
}
QString NodeMetaReader::GetStringForCurrentLanguage(const QMap<QString, QString> *language_map)
{
if (language_map->isEmpty()) {
// There are no entries for this map, this must be an empty string
return QString();
}
// Get current language config
QString language = Config::Current()["Language"].toString();
// See if our map has an exact language match
QString str_for_lang = language_map->value(language);
if (!str_for_lang.isEmpty()) {
return str_for_lang;
}
// If not, try to find a match with the same language but not the same derivation
QString base_lang = language.split('_').first();
QList<QString> available_languages = language_map->keys();
foreach (const QString& l, available_languages) {
if (l.startsWith(base_lang)) {
// This is the same language, so we can return this
return language_map->value(l);
}
}
// We couldn't find an exact or close match, just return the first in the list
// (assume a string in the wrong language is better than no string at all)
return language_map->first();
}
NodeInput *NodeMetaReader::GetInputWithID(const QString &id) const
{
foreach (NodeInput* input, inputs_) {
if (input->id() == id) {
return input;
}
}
return nullptr;
}
+61
View File
@@ -0,0 +1,61 @@
#ifndef NODEMETAREADER_H
#define NODEMETAREADER_H
#include <QMap>
#include <QString>
#include <QXmlStreamReader>
#include "input.h"
class NodeMetaReader
{
public:
NodeMetaReader(const QString& xml_meta_filename);
QString Name() const;
const QString& id() const;
QString Category() const;
QString Description() const;
const QString& filename() const;
const QString& frag_code() const;
const QString& vert_code() const;
const int& iterations() const;
NodeInput* iteration_input() const;
const QList<NodeInput*>& inputs() const;
void Retranslate();
private:
void XMLReadLanguageString(QXmlStreamReader* reader, QMap<QString, QString>* map);
void XMLReadEffect(QXmlStreamReader *reader);
void XMLReadIterations(QXmlStreamReader* reader);
void XMLReadParam(QXmlStreamReader* reader);
void XMLReadShader(QXmlStreamReader* reader, QString& destination);
static QString GetStringForCurrentLanguage(const QMap<QString, QString> *language_map);
NodeInput* GetInputWithID(const QString& id) const;
QString xml_filename_;
QMap<QString, QString> names_;
QMap<QString, QString> descriptions_;
QMap<QString, QString> categories_;
QMap<QString, QMap<QString, QString> > param_names_;
QString id_;
QString frag_code_;
QString vert_code_;
int iterations_;
NodeInput* iteration_input_;
QList<NodeInput*> inputs_;
};
#endif // NODEMETAREADER_H
+2 -2
View File
@@ -287,6 +287,8 @@ public:
void SetPosition(const QPointF& pos);
static QString ReadFileAsString(const QString& filename);
protected:
void AddInput(NodeInput* input);
@@ -296,8 +298,6 @@ protected:
virtual void DependentEdgeChanged(NodeInput* from);
static QString ReadFileAsString(const QString& filename);
public slots:
signals: