/*** Olive - Non-Linear Video Editor Copyright (C) 2019 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "input.h" #include #include #include #include #include "common/bezier.h" #include "common/lerp.h" #include "common/tohex.h" #include "common/xmlutils.h" #include "node.h" #include "output.h" #include "inputarray.h" #include "project/item/footage/stream.h" #include "render/color.h" OLIVE_NAMESPACE_ENTER NodeInput::NodeInput(const QString& id, const DataType &type, const QVector &default_value) : NodeParam(id) { Init(type); if (!default_value.isEmpty()) { SetDefaultValue(default_value); } } NodeInput::NodeInput(const QString &id, const NodeParam::DataType &type, const QVariant &default_value) : NodeParam(id) { Init(type); SetDefaultValue(split_normal_value_into_track_values(default_value)); } NodeInput::NodeInput(const QString &id, const NodeParam::DataType &type) : NodeParam(id) { Init(type); } bool NodeInput::IsArray() const { return false; } NodeParam::Type NodeInput::type() { return kInput; } QString NodeInput::name() { if (name_.isEmpty()) { return tr("Input"); } return NodeParam::name(); } void NodeInput::Load(QXmlStreamReader *reader, XMLNodeData &xml_node_data, const QAtomicInt *cancelled) { XMLAttributeLoop(reader, attr) { if (cancelled && *cancelled) { return; } if (attr.name() == QStringLiteral("keyframing")) { set_is_keyframing(attr.value() == QStringLiteral("1")); } } while (XMLReadNextStartElement(reader)) { if (cancelled && *cancelled) { return; } if (reader->name() == QStringLiteral("standard")) { // Load standard value int val_index = 0; while (XMLReadNextStartElement(reader)) { if (cancelled && *cancelled) { return; } if (reader->name() == QStringLiteral("value")) { QString value_text = reader->readElementText(); if (value_text.isEmpty()) { standard_value_.replace(val_index, QVariant()); } else { standard_value_.replace(val_index, StringToValue(value_text, xml_node_data.footage_connections)); } val_index++; } else { reader->skipCurrentElement(); } } } else if (reader->name() == QStringLiteral("keyframes")) { int track = 0; while (XMLReadNextStartElement(reader)) { if (cancelled && *cancelled) { return; } if (reader->name() == QStringLiteral("track")) { while (XMLReadNextStartElement(reader)) { if (cancelled && *cancelled) { return; } if (reader->name() == QStringLiteral("key")) { rational key_time; NodeKeyframe::Type key_type; QVariant key_value; QPointF key_in_handle; QPointF key_out_handle; XMLAttributeLoop(reader, attr) { if (cancelled && *cancelled) { return; } if (attr.name() == QStringLiteral("time")) { key_time = rational::fromString(attr.value().toString()); } else if (attr.name() == QStringLiteral("type")) { key_type = static_cast(attr.value().toInt()); } else if (attr.name() == QStringLiteral("inhandlex")) { key_in_handle.setX(attr.value().toDouble()); } else if (attr.name() == QStringLiteral("inhandley")) { key_in_handle.setY(attr.value().toDouble()); } else if (attr.name() == QStringLiteral("outhandlex")) { key_out_handle.setX(attr.value().toDouble()); } else if (attr.name() == QStringLiteral("outhandley")) { key_out_handle.setY(attr.value().toDouble()); } } key_value = StringToValue(reader->readElementText(), xml_node_data.footage_connections); NodeKeyframePtr key = NodeKeyframe::Create(key_time, key_value, key_type, track); key->set_bezier_control_in(key_in_handle); key->set_bezier_control_out(key_out_handle); key->set_parent(this); keyframe_tracks_[track].append(key); } else { reader->skipCurrentElement(); } } track++; } else { reader->skipCurrentElement(); } } } else if (reader->name() == QStringLiteral("connections")) { while (XMLReadNextStartElement(reader)) { if (cancelled && *cancelled) { return; } if (reader->name() == QStringLiteral("connection")) { xml_node_data.desired_connections.append({this, reader->readElementText().toULongLong()}); } else { reader->skipCurrentElement(); } } } else if (reader->name() == QStringLiteral("csinput")) { set_property(QStringLiteral("col_input"), reader->readElementText()); } else if (reader->name() == QStringLiteral("csdisplay")) { set_property(QStringLiteral("col_display"), reader->readElementText()); } else if (reader->name() == QStringLiteral("csview")) { set_property(QStringLiteral("col_view"), reader->readElementText()); } else if (reader->name() == QStringLiteral("cslook")) { set_property(QStringLiteral("col_look"), reader->readElementText()); } else { LoadInternal(reader, xml_node_data, cancelled); } } } void NodeInput::Save(QXmlStreamWriter *writer) const { writer->writeStartElement("input"); writer->writeAttribute("id", id()); writer->writeAttribute("keyframing", QString::number(keyframing_)); // Write standard value writer->writeStartElement("standard"); foreach (const QVariant& v, standard_value_) { writer->writeTextElement("value", ValueToString(v)); } writer->writeEndElement(); // standard // Write keyframes writer->writeStartElement("keyframes"); foreach (const KeyframeTrack& track, keyframe_tracks()) { writer->writeStartElement("track"); foreach (NodeKeyframePtr key, track) { writer->writeStartElement("key"); writer->writeAttribute("time", key->time().toString()); writer->writeAttribute("type", QString::number(key->type())); writer->writeAttribute("inhandlex", QString::number(key->bezier_control_in().x())); writer->writeAttribute("inhandley", QString::number(key->bezier_control_in().y())); writer->writeAttribute("outhandlex", QString::number(key->bezier_control_out().x())); writer->writeAttribute("outhandley", QString::number(key->bezier_control_out().y())); writer->writeCharacters(ValueToString(key->value())); writer->writeEndElement(); // key } writer->writeEndElement(); // track } writer->writeEndElement(); // keyframes if (data_type_ == NodeParam::kColor) { // Save color management information writer->writeTextElement(QStringLiteral("csinput"), get_property(QStringLiteral("col_input")).toString()); writer->writeTextElement(QStringLiteral("csdisplay"), get_property(QStringLiteral("col_display")).toString()); writer->writeTextElement(QStringLiteral("csview"), get_property(QStringLiteral("col_view")).toString()); writer->writeTextElement(QStringLiteral("cslook"), get_property(QStringLiteral("col_look")).toString()); } SaveConnections(writer); SaveInternal(writer); writer->writeEndElement(); // input } void NodeInput::SaveConnections(QXmlStreamWriter *writer) const { writer->writeStartElement("connections"); foreach (NodeEdgePtr edge, edges_) { writer->writeTextElement("connection", QString::number(reinterpret_cast(edge->output()))); } writer->writeEndElement(); // connections } const NodeParam::DataType &NodeInput::data_type() const { return data_type_; } void NodeInput::LoadInternal(QXmlStreamReader* reader, XMLNodeData &, const QAtomicInt*) { reader->skipCurrentElement(); } void NodeInput::SaveInternal(QXmlStreamWriter*) const { } void NodeInput::Init(DataType type) { keyframable_ = true; keyframing_ = false; data_type_ = type; int track_size; switch (data_type_) { case kVec2: track_size = 2; break; case kVec3: track_size = 3; break; case kVec4: case kColor: track_size = 4; break; default: track_size = 1; } keyframe_tracks_.resize(track_size); standard_value_.resize(track_size); } void NodeInput::SetDefaultValue(const QVector &default_value) { default_value_ = default_value; for (int i=0;i().toString(); case kFootage: return QString::number(reinterpret_cast(value.value().get())); default: if (value.canConvert()) { return value.toString(); } if (!value.isNull()) { qWarning() << "Failed to convert type" << ToHex(data_type) << "to string"; } /* fall through */ // These data types need no XML representation case kTexture: case kSamples: case kBuffer: return QString(); } } QVariant NodeInput::StringToValue(const DataType& data_type, const QString &string) { switch (data_type) { case kRational: return QVariant::fromValue(rational::fromString(string)); default: return string; } } void NodeInput::GetDependencies(QList &list, bool traverse, bool exclusive_only) const { if (is_connected() && (get_connected_output()->edges().size() == 1 || !exclusive_only)) { Node* connected = get_connected_node(); if (!list.contains(connected)) { list.append(connected); if (traverse) { QList connected_inputs = connected->GetInputsIncludingArrays(); foreach (NodeInput* i, connected_inputs) { i->GetDependencies(list, traverse, exclusive_only); } } } } } QVariant NodeInput::GetDefaultValue() const { if (default_value_.isEmpty()) { return QVariant(); } return combine_track_values_into_normal_value(default_value_); } QVariant NodeInput::GetDefaultValueForTrack(int track) const { if (default_value_.isEmpty()) { return QVariant(); } return default_value_.at(track); } QList NodeInput::GetDependencies(bool traverse, bool exclusive_only) const { QList list; GetDependencies(list, traverse, exclusive_only); return list; } QList NodeInput::GetExclusiveDependencies() const { return GetDependencies(true, true); } QList NodeInput::GetImmediateDependencies() const { return GetDependencies(false, false); } QVariant NodeInput::StringToValue(const QString &string, QList& footage_connections) { if (data_type_ == NodeParam::kFootage) { footage_connections.append({this, string.toULongLong()}); } return StringToValue(data_type_, string); } NodeOutput *NodeInput::get_connected_output() const { if (!edges_.isEmpty()) { return edges_.first()->output(); } return nullptr; } Node *NodeInput::get_connected_node() const { NodeOutput* output = get_connected_output(); if (output) { return output->parentNode(); } return nullptr; } bool NodeInput::type_can_be_interpolated(NodeParam::DataType type) { return type == kFloat || type == kVec2 || type == kVec3 || type == kVec4 || type == kColor; } QVariant NodeInput::get_value_at_time(const rational &time) const { return combine_track_values_into_normal_value(get_split_values_at_time(time)); } QVector NodeInput::get_split_values_at_time(const rational &time) const { QVector vals; for (int i=0;itime() >= time) { // This time precedes any keyframe, so we just return the first value return key_track.first()->value(); } if (key_track.last()->time() <= time) { // This time is after any keyframes so we return the last value return key_track.last()->value(); } // If we're here, the time must be somewhere in between the keyframes for (int i=0;itime() == time || !type_can_be_interpolated(data_type()) || (before->time() < time && before->type() == NodeKeyframe::kHold)) { // Time == keyframe time, so value is precise return before->value(); } else if (after->time() == time) { // Time == keyframe time, so value is precise return after->value(); } else if (before->time() < time && after->time() > time) { // We must interpolate between these keyframes if (before->type() == NodeKeyframe::kBezier && after->type() == NodeKeyframe::kBezier) { // Perform a cubic bezier with two control points double t = Bezier::CubicXtoT(time.toDouble(), before->time().toDouble(), before->time().toDouble() + before->bezier_control_out().x(), after->time().toDouble() + after->bezier_control_in().x(), after->time().toDouble()); double y = Bezier::CubicTtoY(before->value().toDouble(), before->value().toDouble() + before->bezier_control_out().y(), after->value().toDouble() + after->bezier_control_in().y(), after->value().toDouble(), t); return y; } else if (before->type() == NodeKeyframe::kBezier || after->type() == NodeKeyframe::kBezier) { // Perform a quadratic bezier with only one control point QPointF control_point; double control_point_time; double control_point_value; if (before->type() == NodeKeyframe::kBezier) { control_point = before->bezier_control_out(); control_point_time = before->time().toDouble() + control_point.x(); control_point_value = before->value().toDouble() + control_point.y(); } else { control_point = after->bezier_control_in(); control_point_time = after->time().toDouble() + control_point.x(); control_point_value = after->value().toDouble() + control_point.y(); } // Generate T from time values - used to determine bezier progress double t = Bezier::QuadraticXtoT(time.toDouble(), before->time().toDouble(), control_point_time, after->time().toDouble()); // Generate value using T double y = Bezier::QuadraticTtoY(before->value().toDouble(), control_point_value, after->value().toDouble(), t); return y; } else { // To have arrived here, the keyframes must both be linear qreal period_progress = (time.toDouble() - before->time().toDouble()) / (after->time().toDouble() - before->time().toDouble()); return lerp(before->value().toDouble(), after->value().toDouble(), period_progress); } } } } return standard_value_.at(track); } QList NodeInput::get_keyframe_at_time(const rational &time) const { QList keys; for (int i=0;itime() == time) { return key; } } } return nullptr; } NodeKeyframePtr NodeInput::get_closest_keyframe_to_time_on_track(const rational &time, int track) const { if (is_using_standard_value(track)) { return nullptr; } const KeyframeTrack& key_track = keyframe_tracks_.at(track); if (time <= key_track.first()->time()) { return key_track.first(); } if (time >= key_track.last()->time()) { return key_track.last(); } for (int i=1;itime() <= time && next_key->time() >= time) { // Return whichever is closer rational prev_diff = time - prev_key->time(); rational next_diff = next_key->time() - time; if (next_diff < prev_diff) { return next_key; } else { return prev_key; } } } return nullptr; } NodeKeyframePtr NodeInput::get_closest_keyframe_before_time(const rational &time) const { NodeKeyframePtr key = nullptr; foreach (const KeyframeTrack& track, keyframe_tracks_) { foreach (NodeKeyframePtr k, track) { if (k->time() >= time) { break; } else if (!key || k->time() > key->time()) { key = k; } } } return key; } NodeKeyframePtr NodeInput::get_closest_keyframe_after_time(const rational &time) const { NodeKeyframePtr key = nullptr; foreach (const KeyframeTrack& track, keyframe_tracks_) { for (int i=track.size()-1;i>=0;i--) { NodeKeyframePtr k = track.at(i); if (k->time() <= time) { break; } else if (!key || k->time() < key->time()) { key = k; } } } return key; } NodeKeyframe::Type NodeInput::get_best_keyframe_type_for_time(const rational &time, int track) const { NodeKeyframePtr closest_key = get_closest_keyframe_to_time_on_track(time, track); if (closest_key) { return closest_key->type(); } return NodeKeyframe::kDefaultType; } int NodeInput::get_number_of_keyframe_tracks() const { return keyframe_tracks_.size(); } NodeKeyframePtr NodeInput::get_earliest_keyframe() const { NodeKeyframePtr earliest = nullptr; foreach (const KeyframeTrack& track, keyframe_tracks_) { if (!track.isEmpty()) { NodeKeyframePtr earliest_in_track = track.first(); if (!earliest || earliest_in_track->time() < earliest->time()) { earliest = earliest_in_track; } } } return earliest; } NodeKeyframePtr NodeInput::get_latest_keyframe() const { NodeKeyframePtr latest = nullptr; foreach (const KeyframeTrack& track, keyframe_tracks_) { if (!track.isEmpty()) { NodeKeyframePtr latest_in_track = track.last(); if (!latest || latest_in_track->time() > latest->time()) { latest = latest_in_track; } } } return latest; } void NodeInput::insert_keyframe(NodeKeyframePtr key) { Q_ASSERT(is_keyframable()); insert_keyframe_internal(key); connect(key.get(), &NodeKeyframe::TimeChanged, this, &NodeInput::KeyframeTimeChanged); connect(key.get(), &NodeKeyframe::ValueChanged, this, &NodeInput::KeyframeValueChanged); connect(key.get(), &NodeKeyframe::TypeChanged, this, &NodeInput::KeyframeTypeChanged); connect(key.get(), &NodeKeyframe::BezierControlInChanged, this, &NodeInput::KeyframeBezierInChanged); connect(key.get(), &NodeKeyframe::BezierControlOutChanged, this, &NodeInput::KeyframeBezierOutChanged); emit KeyframeAdded(key); emit_range_affected_by_keyframe(key.get()); } void NodeInput::remove_keyframe(NodeKeyframePtr key) { Q_ASSERT(is_keyframable()); TimeRange time_affected = get_range_affected_by_keyframe(key.get()); disconnect(key.get(), &NodeKeyframe::TimeChanged, this, &NodeInput::KeyframeTimeChanged); disconnect(key.get(), &NodeKeyframe::ValueChanged, this, &NodeInput::KeyframeValueChanged); disconnect(key.get(), &NodeKeyframe::TypeChanged, this, &NodeInput::KeyframeTypeChanged); disconnect(key.get(), &NodeKeyframe::BezierControlInChanged, this, &NodeInput::KeyframeBezierInChanged); disconnect(key.get(), &NodeKeyframe::BezierControlOutChanged, this, &NodeInput::KeyframeBezierOutChanged); keyframe_tracks_[key->track()].removeOne(key); key->set_parent(nullptr); emit KeyframeRemoved(key); emit_time_range(time_affected); } NodeKeyframePtr NodeInput::get_keyframe_shared_ptr_from_raw(NodeKeyframe* raw) const { foreach (const KeyframeTrack& track, keyframe_tracks_) { foreach (NodeKeyframePtr key, track) { if (key.get() == raw) { return key; } } } return nullptr; } void NodeInput::KeyframeTimeChanged() { NodeKeyframe* key = static_cast(sender()); int keyframe_index = FindIndexOfKeyframeFromRawPtr(key); Q_ASSERT(keyframe_index > -1); TimeRange original_range = get_range_around_index(keyframe_index, key->track()); if (!(original_range.in() < key->time() && original_range.out() > key->time())) { // This keyframe needs resorting, store it and remove it from the list NodeKeyframePtr key_shared_ptr = keyframe_tracks_.at(key->track()).at(keyframe_index); keyframe_tracks_[key->track()].removeAt(keyframe_index); // Automatically insertion sort insert_keyframe_internal(key_shared_ptr); // Invalidate new area that the keyframe has been moved to emit_time_range(get_range_around_index(FindIndexOfKeyframeFromRawPtr(key), key->track())); } // Invalidate entire area surrounding the keyframe (either where it currently is, or where it used to be before it // was resorted in the if block above) emit_time_range(original_range); } void NodeInput::KeyframeValueChanged() { emit_range_affected_by_keyframe(static_cast(sender())); } void NodeInput::KeyframeTypeChanged() { NodeKeyframe* key = static_cast(sender()); int keyframe_index = FindIndexOfKeyframeFromRawPtr(key); if (keyframe_tracks_.at(key->track()).size() == 1) { // If there are no other frames, the interpolation won't do anything return; } // Invalidate entire range emit_time_range(get_range_around_index(keyframe_index, key->track())); } void NodeInput::KeyframeBezierInChanged() { NodeKeyframe* key = static_cast(sender()); int keyframe_index = FindIndexOfKeyframeFromRawPtr(key); rational start = RATIONAL_MIN; rational end = key->time(); if (keyframe_index > 0) { start = keyframe_tracks_.at(key->track()).at(keyframe_index - 1)->time(); } emit ValueChanged(TimeRange(start, end)); } void NodeInput::KeyframeBezierOutChanged() { NodeKeyframe* key = static_cast(sender()); int keyframe_index = FindIndexOfKeyframeFromRawPtr(key); rational start = key->time(); rational end = RATIONAL_MAX; if (keyframe_index < keyframe_tracks_.at(key->track()).size() - 1) { end = keyframe_tracks_.at(key->track()).at(keyframe_index + 1)->time(); } emit ValueChanged(TimeRange(start, end)); } int NodeInput::FindIndexOfKeyframeFromRawPtr(NodeKeyframe *raw_ptr) const { const KeyframeTrack& track = keyframe_tracks_.at(raw_ptr->track()); for (int i=0;itrack()]; key->set_parent(this); for (int i=0;itime() != key->time()); if (compare->time() > key->time()) { key_track.insert(i, key); return; } } key_track.append(key); } bool NodeInput::is_using_standard_value(int track) const { return (!is_keyframing() || keyframe_tracks_.at(track).isEmpty()); } TimeRange NodeInput::get_range_affected_by_keyframe(NodeKeyframe *key) const { int keyframe_index = FindIndexOfKeyframeFromRawPtr(key); TimeRange range = get_range_around_index(keyframe_index, key->track()); const KeyframeTrack& key_track = keyframe_tracks_.at(key->track()); // If a previous key exists and it's a hold, we don't need to invalidate those frames if (key_track.size() > 1 && keyframe_index > 0 && key_track.at(keyframe_index - 1)->type() == NodeKeyframe::kHold) { range.set_in(key->time()); } return range; } TimeRange NodeInput::get_range_around_index(int index, int track) const { rational range_begin = RATIONAL_MIN; rational range_end = RATIONAL_MAX; const KeyframeTrack& key_track = keyframe_tracks_.at(track); if (key_track.size() > 1) { if (index > 0) { // If this is not the first key, we'll need to limit it to the key just before range_begin = key_track.at(index - 1)->time(); } if (index < key_track.size() - 1) { // If this is not the last key, we'll need to limit it to the key just after range_end = key_track.at(index + 1)->time(); } } return TimeRange(range_begin, range_end); } void NodeInput::emit_time_range(const TimeRange &range) { emit ValueChanged(range); } void NodeInput::emit_range_affected_by_keyframe(NodeKeyframe *key) { emit_time_range(get_range_affected_by_keyframe(key)); } bool NodeInput::has_keyframe_at_time(const rational &time) const { if (!is_keyframing()) { return false; } // Loop through keyframes to see if any match foreach (const KeyframeTrack& track, keyframe_tracks_) { foreach (NodeKeyframePtr key, track) { if (key->time() == time) { return true; } } } // None match return false; } bool NodeInput::is_keyframing() const { return keyframing_; } void NodeInput::set_is_keyframing(bool k) { keyframing_ = k; emit KeyframeEnableChanged(keyframing_); } bool NodeInput::is_keyframable() const { return keyframable_; } QVariant NodeInput::get_standard_value() const { return combine_track_values_into_normal_value(standard_value_); } const QVector &NodeInput::get_split_standard_value() const { return standard_value_; } void NodeInput::set_standard_value(const QVariant &value, int track) { standard_value_.replace(track, value); if (is_using_standard_value(track)) { // If this standard value is being used, we need to send a value changed signal emit ValueChanged(TimeRange(RATIONAL_MIN, RATIONAL_MAX)); } } const QVector &NodeInput::keyframe_tracks() const { return keyframe_tracks_; } void NodeInput::set_is_keyframable(bool k) { keyframable_ = k; } void NodeInput::CopyValues(NodeInput *source, NodeInput *dest, bool include_connections) { Q_ASSERT(source->id() == dest->id()); // Copy standard value dest->standard_value_ = source->standard_value_; // Copy keyframes for (int i=0;ikeyframe_tracks_.size();i++) { dest->keyframe_tracks_[i].clear(); foreach (NodeKeyframePtr key, source->keyframe_tracks_.at(i)) { dest->keyframe_tracks_[i].append(key->copy()); } } // Copy keyframing state dest->set_is_keyframing(source->is_keyframing()); // Copy connections if (include_connections && source->get_connected_output() != nullptr) { ConnectEdge(source->get_connected_output(), dest); } // If these inputs are an array, copy the subparams too if (dest->IsArray()) { NodeInputArray* src_array = static_cast(source); NodeInputArray* dst_array = static_cast(dest); dst_array->SetSize(src_array->GetSize()); for (int i=0;iGetSize();i++) { CopyValues(src_array->At(i), dst_array->At(i), include_connections); } } emit dest->ValueChanged(TimeRange(RATIONAL_MIN, RATIONAL_MAX)); } void NodeInput::set_property(const QString &key, const QVariant &value) { properties_.insert(key, value); emit PropertyChanged(key, value); } QVariant NodeInput::get_property(const QString &key) const { return properties_.value(key); } bool NodeInput::has_property(const QString &key) const { return properties_.contains(key); } const QHash &NodeInput::properties() const { return properties_; } QVector NodeInput::split_normal_value_into_track_values(const QVariant &value) const { QVector vals(get_number_of_keyframe_tracks()); switch (data_type_) { case kVec2: { QVector2D vec = value.value(); vals.replace(0, vec.x()); vals.replace(1, vec.y()); break; } case kVec3: { QVector3D vec = value.value(); vals.replace(0, vec.x()); vals.replace(1, vec.y()); vals.replace(2, vec.z()); break; } case kVec4: { QVector4D vec = value.value(); vals.replace(0, vec.x()); vals.replace(1, vec.y()); vals.replace(2, vec.z()); vals.replace(3, vec.w()); break; } case kColor: { Color c = value.value(); vals.replace(0, c.red()); vals.replace(1, c.green()); vals.replace(2, c.blue()); vals.replace(3, c.alpha()); break; } default: vals.replace(0, value); } return vals; } QVariant NodeInput::combine_track_values_into_normal_value(const QVector &split) const { switch (data_type_) { case kVec2: { return QVector2D(split.at(0).toFloat(), split.at(1).toFloat()); } case kVec3: { return QVector3D(split.at(0).toFloat(), split.at(1).toFloat(), split.at(2).toFloat()); } case kVec4: { return QVector4D(split.at(0).toFloat(), split.at(1).toFloat(), split.at(2).toFloat(), split.at(3).toFloat()); } case kColor: { return QVariant::fromValue(Color(split.at(0).toFloat(), split.at(1).toFloat(), split.at(2).toFloat(), split.at(3).toFloat())); } default: return split.first(); } } QStringList NodeInput::get_combobox_strings() const { return get_property(QStringLiteral("combo_str")).toStringList(); } void NodeInput::set_combobox_strings(const QStringList &strings) { set_property(QStringLiteral("combo_str"), strings); } OLIVE_NAMESPACE_EXIT