/*** 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 Project; class Folder; /** * @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 }; 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_; } virtual bool IsItem() const { return false; } /** * @brief Function called to retranslate parameter names (should be overridden in derivatives) */ virtual void Retranslate(); virtual QIcon icon() const; virtual QString duration() const {return QString();} virtual qint64 creation_time() const {return 0;} virtual qint64 mod_time() const {return 0;} virtual QString rate() const {return QString();} 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; } 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; 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; } 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); /** * @brief Convert a pointer to a value that can be sent between NodeParams */ static QVariant PtrToValue(void* ptr); template /** * @brief Convert a NodeParam value to a pointer of any kind */ static T* ValueToPtr(const QVariant& ptr); 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 Return whether this Node can be deleted or not */ bool CanBeDeleted() const; /** * @brief Set whether this Node can be deleted in the UI or not */ void SetCanBeDeleted(bool s); /** * @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); void SetFolder(Folder* folder) { folder_ = folder; } class ArrayInsertCommand : public UndoCommand { public: ArrayInsertCommand(Node* node, const QString& input, int index) : node_(node), input_(input), index_(index) { } virtual Project* GetRelevantProject() const override; protected: virtual void redo() override { node_->InputArrayInsert(input_, index_); } virtual void undo() override { node_->InputArrayRemove(input_, index_); } private: Node* node_; QString input_; int index_; }; class ArrayResizeCommand : public UndoCommand { public: ArrayResizeCommand(Node* node, const QString& input, int size) : node_(node), input_(input), size_(size) {} virtual Project* GetRelevantProject() const override; protected: virtual void redo() override { old_size_ = node_->InputArraySize(input_); if (old_size_ > size_) { // Decreasing in size, disconnect any extraneous edges for (int i=size_; iinput_connections().at(input); removed_connections_[input] = output; DisconnectEdge(output, input); } catch (std::out_of_range&) {} } } node_->ArrayResizeInternal(input_, size_); } virtual void undo() override { for (auto it=removed_connections_.cbegin(); it!=removed_connections_.cend(); it++) { ConnectEdge(it->second, it->first); } removed_connections_.clear(); node_->ArrayResizeInternal(input_, old_size_); } private: Node* node_; QString input_; int size_; int old_size_; InputConnections removed_connections_; }; class ArrayRemoveCommand : public UndoCommand { public: ArrayRemoveCommand(Node* node, const QString& input, int index) : node_(node), input_(input), index_(index) { } virtual Project* GetRelevantProject() const override; protected: virtual void redo() override { // Save immediate data if (node_->IsInputKeyframable(input_)) { is_keyframing_ = node_->IsInputKeyframing(input_, index_); } standard_value_ = node_->GetSplitStandardValue(input_, index_); keyframes_ = node_->GetKeyframeTracks(input_, index_); node_->GetImmediate(input_, index_)->delete_all_keyframes(&memory_manager_); node_->InputArrayRemove(input_, index_); } virtual void undo() override { node_->InputArrayInsert(input_, index_); // Restore keyframes foreach (const NodeKeyframeTrack& track, keyframes_) { foreach (NodeKeyframe* key, track) { key->setParent(node_); } } node_->SetSplitStandardValue(input_, standard_value_, index_); if (node_->IsInputKeyframable(input_)) { node_->SetInputIsKeyframing(input_, is_keyframing_, index_); } } private: Node* node_; QString input_; int index_; SplitValue standard_value_; bool is_keyframing_; QVector keyframes_; QObject memory_manager_; }; InputFlags GetInputFlags(const QString& input) const; void SetInputFlags(const QString &input, const InputFlags &f); 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); 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); /** * @brief Don't send cache invalidation signals if `input` is connected or disconnected * * By default, when a node is connected or disconnected from input, the Node assumes that the * parameters has changed throughout the duration of the clip (essential from 0 to infinity). * In some scenarios, it may be preferable to handle this signal separately in order to */ void IgnoreInvalidationsFrom(const QString &input_id); 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 SetFlags(const uint64_t &f) { 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; }; class ImmediateRemoveAllKeyframesCommand : public UndoCommand { public: ImmediateRemoveAllKeyframesCommand(NodeInputImmediate *immediate) : immediate_(immediate) {} virtual Project* GetRelevantProject() const override { return nullptr; } protected: virtual void prepare() override; virtual void redo() override; virtual void undo() override; private: NodeInputImmediate *immediate_; QObject memory_manager_; QVector keys_; }; NodeInputImmediate* CreateImmediate(const QString& input); NodeInputImmediate* GetImmediate(const QString& input, int element) const; 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; void ArrayResizeInternal(const QString& id, int size); 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); QVector ignore_connections_; /** * @brief Internal variable for whether this Node can be deleted or not */ bool can_be_deleted_; /** * @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; } template T* Node::ValueToPtr(const QVariant &ptr) { return reinterpret_cast(ptr.value()); } using NodePtr = std::shared_ptr; class NodeSetPositionCommand : public UndoCommand { public: NodeSetPositionCommand(Node* node, Node* context, const Node::Position& pos) { node_ = node; context_ = context; pos_ = pos; } virtual Project* GetRelevantProject() const override { return node_->project(); } protected: virtual void redo() override; virtual void undo() override; private: Node* node_; Node* context_; Node::Position pos_; Node::Position old_pos_; bool added_; }; class NodeSetPositionAndDependenciesRecursivelyCommand : public UndoCommand{ public: NodeSetPositionAndDependenciesRecursivelyCommand(Node* node, Node* context, const Node::Position& pos) : node_(node), context_(context), pos_(pos) {} virtual Project* GetRelevantProject() const override { return node_->project(); } protected: virtual void prepare() override; virtual void redo() override; virtual void undo() override; private: void move_recursively(Node *node, const QPointF &diff); Node* node_; Node* context_; Node::Position pos_; QVector commands_; }; class NodeRemovePositionFromContextCommand : public UndoCommand { public: NodeRemovePositionFromContextCommand(Node *node, Node *context) : node_(node), context_(context) { } virtual Project * GetRelevantProject() const override { return node_->project(); } protected: virtual void redo() override; virtual void undo() override; private: Node *node_; Node *context_; Node::Position old_pos_; bool contained_; }; class NodeRemovePositionFromAllContextsCommand : public UndoCommand { public: NodeRemovePositionFromAllContextsCommand(Node *node) : node_(node) { } virtual Project * GetRelevantProject() const override { return node_->project(); } protected: virtual void redo() override; virtual void undo() override; private: Node *node_; std::map contexts_; }; } Q_DECLARE_METATYPE(olive::Node::ValueHint) #endif // NODE_H