From e0bfbf12b23cf5d4b9f44cc3230e0a046773ddb6 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 4 Apr 2022 23:34:34 -0700 Subject: [PATCH] groups: improved functionality Various improvements and fixes to groups --- app/node/effect/opacity/opacityeffect.cpp | 47 ++++++++++++++----- app/node/effect/opacity/opacityeffect.h | 12 +++-- app/node/group/group.cpp | 6 +-- app/node/group/group.h | 2 +- app/node/node.cpp | 11 +++++ app/node/node.h | 1 + .../project/serializer/serializer220403.cpp | 4 +- app/shaders/opacity.frag | 11 +++++ app/widget/nodeparamview/nodeparamview.cpp | 2 +- app/widget/nodeview/nodeview.cpp | 47 +++++++++++++++---- app/widget/nodeview/nodeview.h | 2 + 11 files changed, 112 insertions(+), 33 deletions(-) create mode 100644 app/shaders/opacity.frag diff --git a/app/node/effect/opacity/opacityeffect.cpp b/app/node/effect/opacity/opacityeffect.cpp index f951b422b..b8ac2d017 100644 --- a/app/node/effect/opacity/opacityeffect.cpp +++ b/app/node/effect/opacity/opacityeffect.cpp @@ -5,7 +5,10 @@ namespace olive { -#define super NodeGroup +#define super Node + +const QString OpacityEffect::kTextureInput = QStringLiteral("tex_in"); +const QString OpacityEffect::kValueInput = QStringLiteral("opacity_in"); OpacityEffect::OpacityEffect() { @@ -15,24 +18,44 @@ OpacityEffect::OpacityEffect() SetNodePositionInContext(math, QPointF(0, 0)); - tex_in_pass_ = AddInputPassthrough(NodeInput(math, MathNode::kParamAIn), InputFlags(kInputFlagNotKeyframable)); - SetInputDataType(tex_in_pass_, NodeValue::kTexture); + AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); - value_in_pass_ = AddInputPassthrough(NodeInput(math, MathNode::kParamBIn)); - SetInputProperty(value_in_pass_, QStringLiteral("view"), FloatSlider::kPercentage); - SetInputProperty(value_in_pass_, QStringLiteral("min"), 0.0); - SetInputProperty(value_in_pass_, QStringLiteral("max"), 1.0); - math->SetStandardValue(MathNode::kParamBIn, 1.0); - - SetOutputPassthrough(math); + AddInput(kValueInput, NodeValue::kFloat, 1.0); + SetInputProperty(kValueInput, QStringLiteral("view"), FloatSlider::kPercentage); + SetInputProperty(kValueInput, QStringLiteral("min"), 0.0); + SetInputProperty(kValueInput, QStringLiteral("max"), 1.0); } void OpacityEffect::Retranslate() { super::Retranslate(); - SetInputName(tex_in_pass_, tr("Texture")); - SetInputName(value_in_pass_, tr("Opacity")); + SetInputName(kTextureInput, tr("Texture")); + SetInputName(kValueInput, tr("Opacity")); +} + +ShaderCode OpacityEffect::GetShaderCode(const QString &shader_id) const +{ + Q_UNUSED(shader_id) + return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/opacity.frag")); +} + +void OpacityEffect::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +{ + ShaderJob job; + + job.InsertValue(value); + + // If there's no texture, no need to run an operation + if (!job.GetValue(kTextureInput).data().isNull()) { + if (!qFuzzyCompare(job.GetValue(kValueInput).data().toDouble(), 1.0)) { + job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn); + table->Push(NodeValue::kShaderJob, QVariant::fromValue(job), this); + } else { + // 1.0 float is a no-op, so just push the texture + table->Push(job.GetValue(kTextureInput)); + } + } } } diff --git a/app/node/effect/opacity/opacityeffect.h b/app/node/effect/opacity/opacityeffect.h index 82e1f16f6..ac699a813 100644 --- a/app/node/effect/opacity/opacityeffect.h +++ b/app/node/effect/opacity/opacityeffect.h @@ -5,7 +5,7 @@ namespace olive { -class OpacityEffect : public NodeGroup +class OpacityEffect : public Node { public: OpacityEffect(); @@ -21,7 +21,7 @@ public: virtual QString id() const override { - return QStringLiteral("org.olivevideoeditor.Olive.opacityeffect"); + return QStringLiteral("org.olivevideoeditor.Olive.opacity"); } virtual QVector Category() const override @@ -36,9 +36,11 @@ public: virtual void Retranslate() override; -private: - QString tex_in_pass_; - QString value_in_pass_; + virtual ShaderCode GetShaderCode(const QString &shader_id) const override; + virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + + static const QString kTextureInput; + static const QString kValueInput; }; diff --git a/app/node/group/group.cpp b/app/node/group/group.cpp index 20b43caaa..b6f194032 100644 --- a/app/node/group/group.cpp +++ b/app/node/group/group.cpp @@ -58,7 +58,7 @@ void NodeGroup::Retranslate() } } -QString NodeGroup::AddInputPassthrough(const NodeInput &input, const InputFlags &flags, const QString &force_id) +QString NodeGroup::AddInputPassthrough(const NodeInput &input, const QString &force_id) { Q_ASSERT(ContextContainsNode(input.node())); @@ -92,7 +92,7 @@ QString NodeGroup::AddInputPassthrough(const NodeInput &input, const InputFlags Q_ASSERT(!already_exists); } - AddInput(id, input.GetDataType(), input.GetDefaultValue(), input.GetFlags() | flags); + AddInput(id, input.GetDataType(), input.GetDefaultValue(), input.GetFlags()); input_passthroughs_.append({id, input}); @@ -168,7 +168,7 @@ bool NodeGroup::GetInner(NodeInput *input) void NodeGroupAddInputPassthrough::redo() { if (!group_->ContainsInputPassthrough(input_)) { - group_->AddInputPassthrough(input_, InputFlags(), force_id_); + group_->AddInputPassthrough(input_, force_id_); actually_added_ = true; } else { actually_added_ = false; diff --git a/app/node/group/group.h b/app/node/group/group.h index d9407dd3a..fe2151b26 100644 --- a/app/node/group/group.h +++ b/app/node/group/group.h @@ -41,7 +41,7 @@ public: virtual void Retranslate() override; - QString AddInputPassthrough(const NodeInput &input, const InputFlags &flags = InputFlags(), const QString &force_id = QString()); + QString AddInputPassthrough(const NodeInput &input, const QString &force_id = QString()); void RemoveInputPassthrough(const NodeInput &input); diff --git a/app/node/node.cpp b/app/node/node.cpp index daf12f663..506e4044e 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -916,6 +916,17 @@ InputFlags Node::GetInputFlags(const QString &input) const } } +void Node::SetInputFlags(const QString &input, const InputFlags &f) +{ + Input* i = GetInternalInputData(input); + + if (i) { + i->flags = f; + } else { + ReportInvalidInput("set flags of", input); + } +} + void Node::Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const { // Do nothing diff --git a/app/node/node.h b/app/node/node.h index 1b5414d2b..44d704344 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -948,6 +948,7 @@ public: }; InputFlags GetInputFlags(const QString& input) const; + void SetInputFlags(const QString &input, const InputFlags &f); static void SetValueAtTime(const NodeInput &input, const rational &time, const QVariant &value, int track, MultiUndoCommand *command, bool insert_on_all_tracks_if_no_key); diff --git a/app/node/project/serializer/serializer220403.cpp b/app/node/project/serializer/serializer220403.cpp index e8e9187f2..3515e7706 100644 --- a/app/node/project/serializer/serializer220403.cpp +++ b/app/node/project/serializer/serializer220403.cpp @@ -756,7 +756,9 @@ void ProjectSerializer220403::PostConnect(const XMLNodeData &xml_node_data) cons if (Node *input_node = xml_node_data.node_ptrs.value(l.input_node)) { NodeInput resolved(input_node, l.input_id, l.input_element); - l.group->AddInputPassthrough(resolved, l.custom_flags, l.passthrough_id); + l.group->AddInputPassthrough(resolved, l.passthrough_id); + + l.group->SetInputFlags(l.passthrough_id, resolved.GetFlags() | l.custom_flags); if (!l.custom_name.isEmpty()) { l.group->SetInputName(l.passthrough_id, l.custom_name); diff --git a/app/shaders/opacity.frag b/app/shaders/opacity.frag new file mode 100644 index 000000000..b962fd74c --- /dev/null +++ b/app/shaders/opacity.frag @@ -0,0 +1,11 @@ +// Inputs +uniform sampler2D tex_in; +uniform float opacity_in; + +// Input texture coordinate +in vec2 ove_texcoord; +out vec4 frag_color; + +void main() { + frag_color = texture(tex_in, ove_texcoord) * opacity_in; +} diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index 865ab2982..204a675de 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -173,7 +173,7 @@ void NodeParamView::CloseContextsBelongingToProject(Project *p) } } - SetContexts(new_contexts, group_mode_); + SetContexts(new_contexts, new_contexts.isEmpty() ? false : group_mode_); } /*void NodeParamView::SelectNodes(const QVector &nodes) diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 044fd7a15..9105d8cb1 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -268,7 +268,7 @@ void NodeView::Duplicate() for (auto it=src_group->GetInputPassthroughs().cbegin(); it!=src_group->GetInputPassthroughs().cend(); it++) { NodeInput input = it->second; input.set_node(new_nodes.at(selected.indexOf(input.node()))); - dst_group->AddInputPassthrough(input, InputFlags(), it->first); + dst_group->AddInputPassthrough(input, it->first); } dst_group->SetOutputPassthrough(new_nodes.at(selected.indexOf(src_group->GetOutputPassthrough()))); @@ -1235,6 +1235,7 @@ void NodeView::GroupNodes() if (!output_passthrough) { // Default to the first node we find that doesn't output to a node inside the group + output_passthrough = nodes_to_group.first(); foreach (Node *potential_in, nodes_to_group) { if (potential_in != n && !n->OutputsTo(potential_in, false)) { output_passthrough = n; @@ -1443,8 +1444,8 @@ void NodeView::EndEdgeDrag(bool cancel) create_edge_input_item_->SetHighlighted(false); } + NodeInput &creating_input = create_edge_input_; if (create_edge_output_item_ && create_edge_input_item_ && !cancel) { - NodeInput &creating_input = create_edge_input_; if (creating_input.IsValid()) { // Make connection if (!reconnected_to_itself) { @@ -1460,23 +1461,38 @@ void NodeView::EndEdgeDrag(bool cancel) if (creating_input.IsConnected()) { Node::OutputConnection existing_edge_to_remove = {creating_input.GetConnectedOutput(), creating_input}; - command->add_child(new NodeEdgeRemoveCommand(existing_edge_to_remove.first, existing_edge_to_remove.second)); + + Node *already_connected_output = creating_input.GetConnectedOutput(); + NodeViewContext *ctx = GetContextItemFromNodeItem(create_edge_input_item_); + if (ctx && !ctx->GetItemFromMap(already_connected_output)) { + if (QMessageBox::warning(this, QString(), tr("Input \"%1\" is currently connected to node \"%2\", which is not visible in this context. " + "By connecting this, that connection will be removed. Do you wish to continue?").arg(creating_input.name(), already_connected_output->GetLabelAndName()), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) { + cancel = true; + } + } + + if (!cancel) { + command->add_child(new NodeEdgeRemoveCommand(existing_edge_to_remove.first, existing_edge_to_remove.second)); + } } - command->add_child(new NodeEdgeAddCommand(creating_output, creating_input)); + if (!cancel) { + command->add_child(new NodeEdgeAddCommand(creating_output, creating_input)); - // If the output is not in the input's context, add it now. We check the item rather than - // the node itself, because sometimes a node may not be in the context but another node - // representing it will be (e.g. groups) - if (!scene_.context_map().value(create_edge_input_item_->GetContext())->GetItemFromMap(creating_output)) { - command->add_child(new NodeSetPositionCommand(creating_output, create_edge_input_item_->GetContext(), scene_.context_map().value(create_edge_input_item_->GetContext())->MapScenePosToNodePosInContext(create_edge_output_item_->scenePos()))); + // If the output is not in the input's context, add it now. We check the item rather than + // the node itself, because sometimes a node may not be in the context but another node + // representing it will be (e.g. groups) + if (!scene_.context_map().value(create_edge_input_item_->GetContext())->GetItemFromMap(creating_output)) { + command->add_child(new NodeSetPositionCommand(creating_output, create_edge_input_item_->GetContext(), scene_.context_map().value(create_edge_input_item_->GetContext())->MapScenePosToNodePosInContext(create_edge_output_item_->scenePos()))); + } } } - creating_input.Reset(); } } + creating_input.Reset(); create_edge_output_item_ = nullptr; create_edge_input_item_ = nullptr; @@ -1536,6 +1552,17 @@ void NodeView::ResizeOverlay() overlay_view_->resize(this->size()); } +NodeViewContext *NodeView::GetContextItemFromNodeItem(NodeViewItem *item) +{ + QGraphicsItem *i = item; + while ((i = i->parentItem())) { + if (NodeViewContext *nvc = dynamic_cast(i)) { + return nvc; + } + } + return nullptr; +} + void NodeView::SetAttachedItems(const QVector &items) { // Detach anything currently attached diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index 887e52cb7..3c62818db 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -185,6 +185,8 @@ private: NodeViewMiniMap *minimap_; + NodeViewContext *GetContextItemFromNodeItem(NodeViewItem *item); + struct AttachedItem { NodeViewItem* item; Node *node;