/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 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 .
***/
#ifndef NODE_H
#define NODE_H
#include
#include
#include
#include
#include
#include
#include "codec/frame.h"
#include "common/xmlutils.h"
#include "node/gizmo/draggable.h"
#include "node/globals.h"
#include "node/keyframe.h"
#include "node/inputimmediate.h"
#include "node/param.h"
#include "render/audioplaybackcache.h"
#include "render/audiowaveformcache.h"
#include "render/framehashcache.h"
#include "render/job/generatejob.h"
#include "render/job/samplejob.h"
#include "render/job/shaderjob.h"
#include "render/shadercode.h"
#include "splitvalue.h"
namespace olive {
#define NODE_DEFAULT_FUNCTIONS(x) \
NODE_DEFAULT_DESTRUCTOR(x) \
NODE_COPY_FUNCTION(x)
#define NODE_DEFAULT_DESTRUCTOR(x) \
virtual ~x() override {DisconnectAll();}
#define NODE_COPY_FUNCTION(x) \
virtual Node *copy() const override {return new x();}
class Folder;
class Project;
struct SerializedData;
/**
* @brief A single processing unit that can be connected with others to create intricate processing systems
*
* A cornerstone of "visual programming", a node is a single "function" that takes input and returns an output that can
* be connected to other nodes. Inputs can be either user-set or retrieved from the output of another node. By joining
* several nodes together, intricate, highly customizable, and infinitely extensible systems can be made for processing
* data. It can also all be exposed to the user without forcing them to write code or compile anything.
*
* A major example in Olive is the entire rendering workflow. To render a frame, Olive will work through a node graph
* that can be infinitely customized by the user to create images.
*
* This is a simple base class designed to contain all the functionality for this kind of processing connective unit.
* It is an abstract class intended to be subclassed to create nodes with actual functionality.
*/
class Node : public QObject
{
Q_OBJECT
public:
enum CategoryID {
kCategoryUnknown = -1,
kCategoryOutput,
kCategoryGenerator,
kCategoryMath,
kCategoryKeying,
kCategoryFilter,
kCategoryColor,
kCategoryTime,
kCategoryTimeline,
kCategoryTransition,
kCategoryDistort,
kCategoryProject,
kCategoryCount
};
enum Flag {
kNone = 0,
kDontShowInParamView = 0x1,
kVideoEffect = 0x2,
kAudioEffect = 0x4,
kDontShowInCreateMenu = 0x8,
kIsItem = 0x10
};
struct ContextPair {
Node *node;
Node *context;
};
Node();
virtual ~Node() override;
/**
* @brief Creates a clone of the Node
*
* By default, the clone will NOT have the values and connections of the original node. The caller is responsible for
* copying that data with functions like CopyInputs() as copies may be done for different reasons.
*/
virtual Node* copy() const = 0;
/**
* @brief Convenience function - assumes parent is a NodeGraph
*/
Project *parent() const;
Project* project() const;
const uint64_t &GetFlags() const
{
return flags_;
}
/**
* @brief Return the name of the node
*
* This is the node's name shown to the user. This must be overridden by subclasses, and preferably run through the
* translator.
*/
virtual QString Name() const = 0;
/**
* @brief Returns a shortened name of this node if applicable
*
* Defaults to returning Name() but can be overridden.
*/
virtual QString ShortName() const;
/**
* @brief Return the unique identifier of the node
*
* This is used in save files and any other times a specific node must be picked out at runtime. This must be an ID
* completely unique to this node, and preferably in bundle identifier format (e.g. "org.company.Name"). This string
* should NOT be translated.
*/
virtual QString id() const = 0;
/**
* @brief Return the category this node is in (optional for subclassing, but recommended)
*
* In any organized node menus, show the node in this category. If this node should be in a subfolder of a subfolder,
* use a "/" to separate categories (e.g. "Distort/Noise"). The string should not start with a "/" as this will be
* interpreted as an empty string category. This value should be run through a translator as its largely user
* oriented.
*/
virtual QVector Category() const = 0;
/**
* @brief Return a description of this node's purpose (optional for subclassing, but recommended)
*
* A short (1-2 sentence) description of what this node should do to help the user understand its purpose. This should
* be run through a translator.
*/
virtual QString Description() const;
const QString& ToolTip() const
{
return tooltip_;
}
Folder* folder() const
{
return folder_;
}
bool IsItem() const { return flags_ & kIsItem; }
/**
* @brief Function called to retranslate parameter names (should be overridden in derivatives)
*/
virtual void Retranslate();
enum DataType
{
ICON,
DURATION,
CREATED_TIME,
MODIFIED_TIME,
FREQUENCY_RATE
};
virtual QVariant data(const DataType &d) const;
const QVector& inputs() const
{
return input_ids_;
}
virtual QVector IgnoreInputsForRendering() const
{
return QVector();
}
class ActiveElements
{
public:
enum Mode {
kAllElements,
kSpecified,
kNoElements
};
ActiveElements(Mode m = kAllElements)
{
mode_ = m;
}
Mode mode() const { return mode_; }
std::list elements() const { return elements_; }
void add(int e)
{
elements_.push_back(e);
mode_ = kSpecified;
}
private:
Mode mode_;
std::list elements_;
};
virtual ActiveElements GetActiveElementsAtTime(const QString &input, const TimeRange &r) const
{
return ActiveElements::kAllElements;
}
bool HasInputWithID(const QString& id) const
{
return input_ids_.contains(id);
}
bool HasParamWithID(const QString& id) const
{
return HasInputWithID(id);
}
FrameHashCache* video_frame_cache() const
{
return video_cache_;
}
ThumbnailCache* thumbnail_cache() const
{
return thumbnail_cache_;
}
AudioPlaybackCache* audio_playback_cache() const
{
return audio_cache_;
}
AudioWaveformCache* waveform_cache() const
{
return waveform_cache_;
}
virtual TimeRange GetVideoCacheRange() const { return TimeRange(); }
virtual TimeRange GetAudioCacheRange() const { return TimeRange(); }
struct Position
{
Position(const QPointF &p = QPointF(0, 0), bool e = false)
{
position = p;
expanded = e;
}
bool load(QXmlStreamReader *reader);
void save(QXmlStreamWriter *writer) const;
QPointF position;
bool expanded;
inline Position &operator+=(const Position &p)
{
position += p.position;
return *this;
}
inline Position &operator-=(const Position &p)
{
position -= p.position;
return *this;
}
friend inline const Position operator+(Position a, const Position &b)
{
a += b;
return a;
}
friend inline const Position operator-(Position a, const Position &b)
{
a -= b;
return a;
}
};
using PositionMap = QHash;
const PositionMap &GetContextPositions() const
{
return context_positions_;
}
bool IsNodeExpandedInContext(Node *node) const
{
return context_positions_.value(node).expanded;
}
bool ContextContainsNode(Node *node) const
{
return context_positions_.contains(node);
}
Position GetNodePositionDataInContext(Node *node)
{
return context_positions_.value(node);
}
QPointF GetNodePositionInContext(Node *node)
{
return GetNodePositionDataInContext(node).position;
}
bool SetNodePositionInContext(Node *node, const QPointF &pos);
bool SetNodePositionInContext(Node *node, const Position &pos);
void SetNodeExpandedInContext(Node *node, bool e)
{
context_positions_[node].expanded = e;
}
bool RemoveNodeFromContext(Node *node);
/**
* @brief Retrieve the color of this node
*/
Color color() const;
/**
* @brief Same as color() but return a pretty gradient version
*/
QLinearGradient gradient_color(qreal top, qreal bottom) const;
/**
* @brief Uses config and returns either color() for flat shading or gradient for gradient
*/
QBrush brush(qreal top, qreal bottom) const;
int GetOverrideColor() const
{
return override_color_;
}
/**
* @brief Sets the override color. Set to -1 for no override color.
*/
void SetOverrideColor(int index)
{
if (override_color_ != index) {
override_color_ = index;
emit ColorChanged();
}
}
static void ConnectEdge(Node *output, const NodeInput& input);
static void DisconnectEdge(Node *output, const NodeInput& input);
void CopyCacheUuidsFrom(Node *n);
bool AreCachesEnabled() const { return caches_enabled_; }
void SetCachesEnabled(bool e) { caches_enabled_ = e; }
virtual QString GetInputName(const QString& id) const;
void SetInputName(const QString& id, const QString& name);
bool IsInputHidden(const QString& input) const;
bool IsInputConnectable(const QString& input) const;
bool IsInputKeyframable(const QString& input) const;
bool IsInputKeyframing(const QString& input, int element = -1) const;
bool IsInputKeyframing(const NodeInput& input) const
{
return IsInputKeyframing(input.input(), input.element());
}
void SetInputIsKeyframing(const QString& input, bool e, int element = -1);
void SetInputIsKeyframing(const NodeInput& input, bool e)
{
SetInputIsKeyframing(input.input(), e, input.element());
}
bool IsInputConnected(const QString& input, int element = -1) const;
bool IsInputConnected(const NodeInput& input) const
{
return IsInputConnected(input.input(), input.element());
}
virtual bool IsInputConnectedForRender(const QString& input, int element = -1) const
{
return IsInputConnected(input, element);
}
bool IsInputConnectedForRender(const NodeInput& input) const
{
return IsInputConnectedForRender(input.input(), input.element());
}
bool IsInputStatic(const QString& input, int element = -1) const
{
return !IsInputConnected(input, element) && !IsInputKeyframing(input, element);
}
bool IsInputStatic(const NodeInput& input) const
{
return IsInputStatic(input.input(), input.element());
}
Node *GetConnectedOutput(const QString& input, int element = -1) const;
Node *GetConnectedOutput(const NodeInput& input) const
{
return GetConnectedOutput(input.input(), input.element());
}
virtual Node *GetConnectedRenderOutput(const QString& input, int element = -1) const
{
return GetConnectedOutput(input, element);
}
Node *GetConnectedRenderOutput(const NodeInput& input) const
{
return GetConnectedRenderOutput(input.input(), input.element());
}
bool IsUsingStandardValue(const QString& input, int track, int element = -1) const;
NodeValue::Type GetInputDataType(const QString& id) const;
void SetInputDataType(const QString& id, const NodeValue::Type& type);
bool HasInputProperty(const QString& id, const QString& name) const;
QHash GetInputProperties(const QString& id) const;
QVariant GetInputProperty(const QString& id, const QString& name) const;
void SetInputProperty(const QString& id, const QString& name, const QVariant& value);
QVariant GetValueAtTime(const QString& input, const rational& time, int element = -1) const
{
NodeValue::Type type = GetInputDataType(input);
return NodeValue::combine_track_values_into_normal_value(type, GetSplitValueAtTime(input, time, element));
}
QVariant GetValueAtTime(const NodeInput& input, const rational& time)
{
return GetValueAtTime(input.input(), time, input.element());
}
SplitValue GetSplitValueAtTime(const QString& input, const rational& time, int element = -1) const;
SplitValue GetSplitValueAtTime(const NodeInput& input, const rational& time)
{
return GetSplitValueAtTime(input.input(), time, input.element());
}
QVariant GetSplitValueAtTimeOnTrack(const QString& input, const rational& time, int track, int element = -1) const;
QVariant GetSplitValueAtTimeOnTrack(const NodeInput& input, const rational& time, int track) const
{
return GetSplitValueAtTimeOnTrack(input.input(), time, track, input.element());
}
QVariant GetSplitValueAtTimeOnTrack(const NodeKeyframeTrackReference& input, const rational& time) const
{
return GetSplitValueAtTimeOnTrack(input.input(), time, input.track());
}
QVariant GetDefaultValue(const QString& input) const;
SplitValue GetSplitDefaultValue(const QString& input) const;
QVariant GetSplitDefaultValueOnTrack(const QString& input, int track) const;
void SetDefaultValue(const QString& input, const QVariant &val);
void SetSplitDefaultValue(const QString& input, const SplitValue &val);
void SetSplitDefaultValueOnTrack(const QString& input, const QVariant &val, int track);
const QVector& GetKeyframeTracks(const QString& input, int element) const;
const QVector& GetKeyframeTracks(const NodeInput& input) const
{
return GetKeyframeTracks(input.input(), input.element());
}
QVector GetKeyframesAtTime(const QString& input, const rational& time, int element = -1) const;
QVector GetKeyframesAtTime(const NodeInput& input, const rational& time) const
{
return GetKeyframesAtTime(input.input(), time, input.element());
}
NodeKeyframe* GetKeyframeAtTimeOnTrack(const QString& input, const rational& time, int track, int element = -1) const;
NodeKeyframe* GetKeyframeAtTimeOnTrack(const NodeInput& input, const rational& time, int track) const
{
return GetKeyframeAtTimeOnTrack(input.input(), time, track, input.element());
}
NodeKeyframe* GetKeyframeAtTimeOnTrack(const NodeKeyframeTrackReference& input, const rational& time) const
{
return GetKeyframeAtTimeOnTrack(input.input(), time, input.track());
}
NodeKeyframe::Type GetBestKeyframeTypeForTimeOnTrack(const QString& input, const rational& time, int track, int element = -1) const;
NodeKeyframe::Type GetBestKeyframeTypeForTimeOnTrack(const NodeInput& input, const rational& time, int track) const
{
return GetBestKeyframeTypeForTimeOnTrack(input.input(), time, track, input.element());
}
NodeKeyframe::Type GetBestKeyframeTypeForTimeOnTrack(const NodeKeyframeTrackReference& input, const rational& time) const
{
return GetBestKeyframeTypeForTimeOnTrack(input.input(), time, input.track());
}
int GetNumberOfKeyframeTracks(const QString& id) const;
int GetNumberOfKeyframeTracks(const NodeInput& id) const
{
return GetNumberOfKeyframeTracks(id.input());
}
NodeKeyframe* GetEarliestKeyframe(const QString& id, int element = -1) const;
NodeKeyframe* GetEarliestKeyframe(const NodeInput& id) const
{
return GetEarliestKeyframe(id.input(), id.element());
}
NodeKeyframe* GetLatestKeyframe(const QString& id, int element = -1) const;
NodeKeyframe* GetLatestKeyframe(const NodeInput& id) const
{
return GetLatestKeyframe(id.input(), id.element());
}
NodeKeyframe* GetClosestKeyframeBeforeTime(const QString& id, const rational& time, int element = -1) const;
NodeKeyframe* GetClosestKeyframeBeforeTime(const NodeInput& id, const rational& time) const
{
return GetClosestKeyframeBeforeTime(id.input(), time, id.element());
}
NodeKeyframe* GetClosestKeyframeAfterTime(const QString& id, const rational& time, int element = -1) const;
NodeKeyframe* GetClosestKeyframeAfterTime(const NodeInput& id, const rational& time) const
{
return GetClosestKeyframeAfterTime(id.input(), time, id.element());
}
bool HasKeyframeAtTime(const QString& id, const rational& time, int element = -1) const;
bool HasKeyframeAtTime(const NodeInput& id, const rational& time) const
{
return HasKeyframeAtTime(id.input(), time, id.element());
}
QStringList GetComboBoxStrings(const QString& id) const;
QVariant GetStandardValue(const QString& id, int element = -1) const;
QVariant GetStandardValue(const NodeInput& id) const
{
return GetStandardValue(id.input(), id.element());
}
SplitValue GetSplitStandardValue(const QString& id, int element = -1) const;
SplitValue GetSplitStandardValue(const NodeInput& id) const
{
return GetSplitStandardValue(id.input(), id.element());
}
QVariant GetSplitStandardValueOnTrack(const QString& input, int track, int element = -1) const;
QVariant GetSplitStandardValueOnTrack(const NodeKeyframeTrackReference& id) const
{
return GetSplitStandardValueOnTrack(id.input().input(), id.track(), id.input().element());
}
void SetStandardValue(const QString& id, const QVariant& value, int element = -1);
void SetStandardValue(const NodeInput& id, const QVariant& value)
{
SetStandardValue(id.input(), value, id.element());
}
void SetSplitStandardValue(const QString& id, const SplitValue& value, int element = -1);
void SetSplitStandardValue(const NodeInput& id, const SplitValue& value)
{
SetSplitStandardValue(id.input(), value, id.element());
}
void SetSplitStandardValueOnTrack(const QString& id, int track, const QVariant& value, int element = -1);
void SetSplitStandardValueOnTrack(const NodeKeyframeTrackReference& id, const QVariant& value)
{
SetSplitStandardValueOnTrack(id.input().input(), id.track(), value, id.input().element());
}
bool InputIsArray(const QString& id) const;
void InputArrayInsert(const QString& id, int index);
void InputArrayResize(const QString& id, int size);
void InputArrayRemove(const QString& id, int index);
void InputArrayAppend(const QString& id)
{
InputArrayResize(id, InputArraySize(id) + 1);
}
void InputArrayPrepend(const QString& id)
{
InputArrayInsert(id, 0);
}
void InputArrayRemoveLast(const QString& id)
{
InputArrayResize(id, InputArraySize(id) - 1);
}
int InputArraySize(const QString& id) const;
NodeInputImmediate* GetImmediate(const QString& input, int element) const;
NodeInput GetEffectInput()
{
return effect_input_.isEmpty() ? NodeInput() : NodeInput(this, effect_input_);
}
const QString &GetEffectInputID() const
{
return effect_input_;
}
class ValueHint {
public:
explicit ValueHint(const QVector &types = QVector(), int index = -1, const QString &tag = QString()) :
type_(types),
index_(index),
tag_(tag)
{
}
explicit ValueHint(const QVector &types, const QString &tag) :
type_(types),
index_(-1),
tag_(tag)
{
}
explicit ValueHint(int index) :
index_(index)
{
}
explicit ValueHint(const QString &tag) :
index_(-1),
tag_(tag)
{
}
const QVector &types() const { return type_; }
const int &index() const { return index_; }
const QString& tag() const { return tag_; }
void set_type(const QVector &type) { type_ = type; }
void set_index(const int &index) { index_ = index; }
void set_tag(const QString &tag) { tag_ = tag; }
bool load(QXmlStreamReader *reader);
void save(QXmlStreamWriter *writer) const;
private:
QVector type_;
int index_;
QString tag_;
};
const QMap &GetValueHints() const
{
return value_hints_;
}
ValueHint GetValueHintForInput(const QString &input, int element = -1) const
{
return value_hints_.value({input, element});
}
void SetValueHintForInput(const QString &input, const ValueHint &hint, int element = -1);
const NodeKeyframeTrack& GetTrackFromKeyframe(NodeKeyframe* key) const;
using InputConnections = std::map;
/**
* @brief Return map of input connections
*
* Inputs can only have one connection, so the key is the input connected and the value is the
* output that it's connected to.
*/
const InputConnections& input_connections() const
{
return input_connections_;
}
using OutputConnection = std::pair;
using OutputConnections = std::vector;
/**
* @brief Return list of output connections
*
* An output can connect an infinite amount of inputs, so in this map, the key is the output and
* the value is a vector of inputs.
*/
const OutputConnections& output_connections() const
{
return output_connections_;
}
/**
* @brief Return a list of all Nodes that this Node's inputs are connected to (does not include this Node)
*/
QVector GetDependencies() const;
/**
* @brief Returns a list of Nodes that this Node is dependent on, provided no other Nodes are dependent on them
* outside of this hierarchy.
*
* Similar to GetDependencies(), but excludes any Nodes that are used outside the dependency graph of this Node.
*/
QVector GetExclusiveDependencies() const;
/**
* @brief Retrieve immediate dependencies (only nodes that are directly connected to the inputs of this one)
*/
QVector GetImmediateDependencies() const;
struct ShaderRequest
{
ShaderRequest(const QString &shader_id)
{
id = shader_id;
}
ShaderRequest(const QString &shader_id, const QString &shader_stub)
{
id = shader_id;
stub = shader_stub;
}
QString id;
QString stub;
};
/**
* @brief Generate hardware accelerated code for this Node
*/
virtual ShaderCode GetShaderCode(const ShaderRequest &request) const;
/**
* @brief If Value() pushes a ShaderJob, this is the function that will process them.
*/
virtual void ProcessSamples(const NodeValueRow &values, const SampleBuffer &input, SampleBuffer &output, int index) const;
/**
* @brief If Value() pushes a GenerateJob, override this function for the image to create
*
* @param frame
*
* The destination buffer. It will already be allocated and ready for writing to.
*/
virtual void GenerateFrame(FramePtr frame, const GenerateJob &job) const;
/**
* @brief Returns whether this node ever receives an input from a particular node instance
*/
bool InputsFrom(Node* n, bool recursively) const;
/**
* @brief Returns whether this node ever receives an input from a node with a particular ID
*/
bool InputsFrom(const QString& id, bool recursively) const;
/**
* @brief Find inputs that `output` outputs to in order to arrive at this node
*
* Traverse this node's inputs recursively looking for `output`, and return a list of
* edges that `output` uses to get to `this` node.
*/
QVector FindWaysNodeArrivesHere(const Node *output) const;
/**
* @brief Severs all input and output connections
*/
void DisconnectAll();
/**
* @brief Get the human-readable name for any category
*/
static QString GetCategoryName(const CategoryID &c);
enum TransformTimeDirection {
kTransformTowardsInput,
kTransformTowardsOutput
};
/**
* @brief Transforms time from this node through the connections it takes to get to the specified node
*/
TimeRange TransformTimeTo(TimeRange time, Node* target, TransformTimeDirection dir, int path_index);
/**
* @brief Find nodes of a certain type that this Node takes inputs from
*/
template
QVector FindInputNodes(int maximum = 0) const;
/**
* @brief Find nodes of a certain type that this Node takes inputs from
*/
template
static QVector FindInputNodesConnectedToInput(const NodeInput &input, int maximum = 0);
using InvalidateCacheOptions = QHash;
/**
* @brief Signal all dependent Nodes that anything cached between start_range and end_range is now invalid and
* requires re-rendering
*
* Override this if your Node subclass keeps a cache, but call this base function at the end of the subclass function.
* Default behavior is to relay this signal to all connected outputs, which will need to be done as to not break
* the DAG. Even if the time needs to be transformed somehow (e.g. converting media time to sequence time), you can
* call this function with transformed time and relay the signal that way.
*/
virtual void InvalidateCache(const TimeRange& range, const QString& from, int element = -1, InvalidateCacheOptions options = InvalidateCacheOptions());
void InvalidateCache(const TimeRange& range, const NodeInput& from, const InvalidateCacheOptions &options = InvalidateCacheOptions())
{
InvalidateCache(range, from.input(), from.element(), options);
}
/**
* @brief Adjusts time that should be sent to nodes connected to certain inputs.
*
* If this node modifies the `time` (i.e. a clip converting sequence time to media time), this function should be
* overridden to do so. Also make sure to override OutputTimeAdjustment() to provide the inverse function.
*/
virtual TimeRange InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time, bool clamp) const;
/**
* @brief The inverse of InputTimeAdjustment()
*/
virtual TimeRange OutputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const;
/**
* @brief Copies inputs from from Node to another including connections
*
* Nodes must be of the same types (i.e. have the same ID)
*/
static void CopyInputs(const Node *source, Node* destination, bool include_connections = true, MultiUndoCommand *command = nullptr);
static void CopyInput(const Node *src, Node* dst, const QString& input, bool include_connections, bool traverse_arrays, MultiUndoCommand *command);
static void CopyValuesOfElement(const Node* src, Node* dst, const QString& input, int src_element, int dst_element, MultiUndoCommand *command = nullptr);
static void CopyValuesOfElement(const Node* src, Node* dst, const QString& input, int element, MultiUndoCommand *command = nullptr)
{
return CopyValuesOfElement(src, dst, input, element, element, command);
}
/**
* @brief Clones a set of nodes and connects the new ones the way the old ones were
*/
static QVector CopyDependencyGraph(const QVector& nodes, MultiUndoCommand *command);
static void CopyDependencyGraph(const QVector& src, const QVector& dst, MultiUndoCommand *command);
static Node* CopyNodeAndDependencyGraphMinusItems(Node* node, MultiUndoCommand* command);
static Node* CopyNodeInGraph(Node *node, MultiUndoCommand* command);
/**
* @brief The main processing function
*
* The node's main purpose is to take values from inputs to set values in outputs. For whatever subclass node you
* create, this is where the code for that goes.
*
* Note that as a video editor, the node graph has to work across time. Depending on the purpose of your node, it may
* output different values depending on the time, and even if not, it will likely be receiving different input
* depending on the time. Most of the difficult work here is handled by NodeInput::get_value() which you should pass
* the `time` parameter to. It will return its value (at that time, if it's keyframed), or pass the time to a
* corresponding output if it's connected to one. If your node doesn't directly deal with time, the default behavior
* of the NodeParam objects will handle everything related to it automatically.
*/
virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const;
bool HasGizmos() const
{
return !gizmos_.isEmpty();
}
const QVector &GetGizmos() const
{
return gizmos_;
}
virtual QTransform GizmoTransformation(const NodeValueRow &row, const NodeGlobals &globals) const { return QTransform(); }
virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals){}
const QString& GetLabel() const;
void SetLabel(const QString& s);
QString GetLabelAndName() const;
QString GetLabelOrName() const;
void InvalidateAll(const QString& input, int element = -1);
bool HasLinks() const
{
return !links_.isEmpty();
}
const QVector& links() const
{
return links_;
}
static bool Link(Node* a, Node* b);
static bool Unlink(Node* a, Node* b);
static bool AreLinked(Node* a, Node* b);
bool Load(QXmlStreamReader *reader, SerializedData *data);
void Save(QXmlStreamWriter *writer) const;
virtual bool LoadCustom(QXmlStreamReader *reader, SerializedData *data);
virtual void SaveCustom(QXmlStreamWriter *writer) const {}
virtual void PostLoadEvent(SerializedData *data);
bool LoadInput(QXmlStreamReader *reader, SerializedData *data);
void SaveInput(QXmlStreamWriter *writer, const QString &id) const;
bool LoadImmediate(QXmlStreamReader *reader, const QString &input, int element, SerializedData *data);
void SaveImmediate(QXmlStreamWriter *writer, const QString &input, int element) const;
void SetFolder(Folder* folder)
{
folder_ = folder;
}
InputFlags GetInputFlags(const QString& input) const;
void SetInputFlag(const QString &input, InputFlag f, bool on = true);
virtual void LoadFinishedEvent(){}
virtual void ConnectedToPreviewEvent(){}
static void SetValueAtTime(const NodeInput &input, const rational &time, const QVariant &value, int track, MultiUndoCommand *command, bool insert_on_all_tracks_if_no_key);
/**
* @brief Find path starting at `from` that outputs to arrive at `to`
*/
static std::list FindPath(Node *from, Node *to, int path_index);
void ArrayResizeInternal(const QString& id, int size);
static const QString kEnabledInput;
protected:
void InsertInput(const QString& id, NodeValue::Type type, const QVariant& default_value, InputFlags flags, int index);
void PrependInput(const QString& id, NodeValue::Type type, const QVariant& default_value, InputFlags flags = InputFlags(kInputFlagNormal))
{
InsertInput(id, type, default_value, flags, 0);
}
void PrependInput(const QString& id, NodeValue::Type type, InputFlags flags = InputFlags(kInputFlagNormal))
{
PrependInput(id, type, QVariant(), flags);
}
void AddInput(const QString& id, NodeValue::Type type, const QVariant& default_value, InputFlags flags = InputFlags(kInputFlagNormal))
{
InsertInput(id, type, default_value, flags, input_ids_.size());
}
void AddInput(const QString& id, NodeValue::Type type, InputFlags flags = InputFlags(kInputFlagNormal))
{
AddInput(id, type, QVariant(), flags);
}
void RemoveInput(const QString& id);
void SetComboBoxStrings(const QString& id, const QStringList& strings)
{
SetInputProperty(id, QStringLiteral("combo_str"), strings);
}
void SendInvalidateCache(const TimeRange &range, const InvalidateCacheOptions &options);
enum GizmoScaleHandles {
kGizmoScaleTopLeft,
kGizmoScaleTopCenter,
kGizmoScaleTopRight,
kGizmoScaleBottomLeft,
kGizmoScaleBottomCenter,
kGizmoScaleBottomRight,
kGizmoScaleCenterLeft,
kGizmoScaleCenterRight,
kGizmoScaleCount,
};
virtual void LinkChangeEvent(){}
virtual void InputValueChangedEvent(const QString& input, int element);
virtual void InputConnectedEvent(const QString& input, int element, Node *output);
virtual void InputDisconnectedEvent(const QString& input, int element, Node *output);
virtual void OutputConnectedEvent(const NodeInput& input);
virtual void OutputDisconnectedEvent(const NodeInput& input);
virtual void childEvent(QChildEvent *event) override;
void SetEffectInput(const QString &input)
{
effect_input_ = input;
}
void SetToolTip(const QString& s)
{
tooltip_ = s;
}
void SetFlag(Flag f, bool on = true)
{
if (on) {
flags_ |= f;
} else {
flags_ &= ~f;
}
}
template
T *AddDraggableGizmo(const QVector &inputs = QVector(), DraggableGizmo::DragValueBehavior behavior = DraggableGizmo::kDeltaFromStart)
{
T *gizmo = new T(this);
gizmo->SetDragValueBehavior(behavior);
foreach (const NodeKeyframeTrackReference &input, inputs) {
gizmo->AddInput(input);
}
connect(gizmo, &DraggableGizmo::HandleStart, this, &Node::GizmoDragStart);
connect(gizmo, &DraggableGizmo::HandleMovement, this, &Node::GizmoDragMove);
return gizmo;
}
template
T *AddDraggableGizmo(const QStringList &inputs, DraggableGizmo::DragValueBehavior behavior = DraggableGizmo::kDeltaFromStart)
{
QVector refs(inputs.size());
for (int i=0; i(refs, behavior);
}
protected slots:
virtual void GizmoDragStart(const olive::NodeValueRow &row, double x, double y, const olive::core::rational &time){}
virtual void GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers){}
signals:
/**
* @brief Signal emitted when SetLabel() is called
*/
void LabelChanged(const QString& s);
void ColorChanged();
void ValueChanged(const NodeInput& input, const TimeRange& range);
void InputConnected(Node *output, const NodeInput& input);
void InputDisconnected(Node *output, const NodeInput& input);
void OutputConnected(Node *output, const NodeInput& input);
void OutputDisconnected(Node *output, const NodeInput& input);
void InputValueHintChanged(const NodeInput& input);
void InputPropertyChanged(const QString& input, const QString& key, const QVariant& value);
void LinksChanged();
void InputArraySizeChanged(const QString& input, int old_size, int new_size);
void KeyframeAdded(NodeKeyframe* key);
void KeyframeRemoved(NodeKeyframe* key);
void KeyframeTimeChanged(NodeKeyframe* key);
void KeyframeTypeChanged(NodeKeyframe* key);
void KeyframeValueChanged(NodeKeyframe* key);
void KeyframeEnableChanged(const NodeInput& input, bool enabled);
void InputAdded(const QString& id);
void InputRemoved(const QString& id);
void InputNameChanged(const QString& id, const QString& name);
void InputDataTypeChanged(const QString& id, NodeValue::Type type);
void AddedToGraph(Project* graph);
void RemovedFromGraph(Project* graph);
void NodeAddedToContext(Node *node);
void NodePositionInContextChanged(Node *node, const QPointF &pos);
void NodeRemovedFromContext(Node *node);
void InputFlagsChanged(const QString &input, const InputFlags &flags);
private:
struct Input {
NodeValue::Type type;
InputFlags flags;
SplitValue default_value;
QHash properties;
QString human_name;
int array_size;
};
NodeInputImmediate* CreateImmediate(const QString& input);
int GetInternalInputIndex(const QString& input) const
{
return input_ids_.indexOf(input);
}
Input* GetInternalInputData(const QString& input)
{
int i = GetInternalInputIndex(input);
if (i == -1) {
return nullptr;
} else {
return &input_data_[i];
}
}
const Input* GetInternalInputData(const QString& input) const
{
int i = GetInternalInputIndex(input);
if (i == -1) {
return nullptr;
} else {
return &input_data_.at(i);
}
}
void ReportInvalidInput(const char* attempted_action, const QString &id, int element) const;
static Node *CopyNodeAndDependencyGraphMinusItemsInternal(QMap &created, Node *node, MultiUndoCommand *command);
/**
* @brief Immediates aren't deleted, so the actual array size may be larger than ArraySize()
*/
int GetInternalInputArraySize(const QString& input);
/**
* @brief Find nodes of a certain type that this Node takes inputs from
*/
template
static void FindInputNodesConnectedToInputInternal(const NodeInput &input, QVector& list, int maximum);
template
static void FindInputNodeInternal(const Node* n, QVector& list, int maximum);
QVector GetDependenciesInternal(bool traverse, bool exclusive_only) const;
void ParameterValueChanged(const QString &input, int element, const olive::core::TimeRange &range);
void ParameterValueChanged(const NodeInput& input, const olive::core::TimeRange &range)
{
ParameterValueChanged(input.input(), input.element(), range);
}
/**
* @brief Intelligently determine how what time range is affected by a keyframe
*/
TimeRange GetRangeAffectedByKeyframe(NodeKeyframe *key) const;
/**
* @brief Gets a time range between the previous and next keyframes of index
*/
TimeRange GetRangeAroundIndex(const QString& input, int index, int track, int element) const;
void ClearElement(const QString &input, int index);
/**
* @brief Custom user label for node
*/
QString label_;
/**
* @brief -1 if the color should be based on the category, >=0 if the user has set a custom color
*/
int override_color_;
/**
* @brief Nodes that are linked with this one
*/
QVector links_;
QVector input_ids_;
QVector input_data_;
QMap standard_immediates_;
QMap > array_immediates_;
InputConnections input_connections_;
OutputConnections output_connections_;
QString tooltip_;
Folder* folder_;
QMap value_hints_;
PositionMap context_positions_;
uint64_t flags_;
QVector gizmos_;
QString effect_input_;
FrameHashCache *video_cache_;
ThumbnailCache *thumbnail_cache_;
AudioPlaybackCache *audio_cache_;
AudioWaveformCache *waveform_cache_;
bool caches_enabled_;
private slots:
/**
* @brief Slot when a keyframe's time changes to keep the keyframes correctly sorted by time
*/
void InvalidateFromKeyframeTimeChange();
/**
* @brief Slot when a keyframe's value changes to signal that the cache needs updating
*/
void InvalidateFromKeyframeValueChange();
/**
* @brief Slot when a keyframe's type changes to signal that the cache needs updating
*/
void InvalidateFromKeyframeTypeChanged();
/**
* @brief Slot when a keyframe's bezier in value changes to signal that the cache needs updating
*/
void InvalidateFromKeyframeBezierInChange();
/**
* @brief Slot when a keyframe's bezier out value changes to signal that the cache needs updating
*/
void InvalidateFromKeyframeBezierOutChange();
};
template
void Node::FindInputNodesConnectedToInputInternal(const NodeInput &input, QVector &list, int maximum)
{
Node* edge = input.GetConnectedOutput();
if (!edge) {
return;
}
T* cast_test = dynamic_cast(edge);
if (cast_test) {
list.append(cast_test);
if (maximum != 0 && list.size() == maximum) {
return;
}
}
FindInputNodeInternal(edge, list, maximum);
}
template
QVector Node::FindInputNodesConnectedToInput(const NodeInput &input, int maximum)
{
QVector list;
FindInputNodesConnectedToInputInternal(input, list, maximum);
return list;
}
template
void Node::FindInputNodeInternal(const Node* n, QVector &list, int maximum)
{
for (auto it=n->input_connections_.cbegin(); it!=n->input_connections_.cend(); it++) {
FindInputNodesConnectedToInputInternal(it->first, list, maximum);
}
}
template
QVector Node::FindInputNodes(int maximum) const
{
QVector list;
FindInputNodeInternal(this, list, maximum);
return list;
}
}
Q_DECLARE_METATYPE(olive::Node::ValueHint)
#endif // NODE_H