From 239a019690bdfd807181ed69a39ac522cf3a40e3 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 29 Nov 2021 21:22:49 -0800 Subject: [PATCH] many improvements and reimplementations --- app/dialog/nodegroup/nodegroupdialog.cpp | 2 + app/node/graph.cpp | 16 + app/node/graph.h | 7 +- app/node/group/group.cpp | 52 +- app/node/group/group.h | 83 ++- app/node/node.cpp | 67 ++- app/node/node.h | 40 +- app/node/param.cpp | 9 + app/node/param.h | 12 + app/widget/nodeview/nodeview.cpp | 268 ++++++---- app/widget/nodeview/nodeview.h | 10 +- app/widget/nodeview/nodeviewcommon.h | 1 + app/widget/nodeview/nodeviewcontext.cpp | 116 ++-- app/widget/nodeview/nodeviewcontext.h | 9 +- app/widget/nodeview/nodeviewedge.cpp | 64 ++- app/widget/nodeview/nodeviewedge.h | 14 +- app/widget/nodeview/nodeviewitem.cpp | 503 ++++++++++-------- app/widget/nodeview/nodeviewitem.h | 90 ++-- app/widget/nodeview/nodeviewitemconnector.cpp | 2 + .../undo/timelineundogeneral.cpp | 2 +- app/window/mainwindow/mainwindow.cpp | 2 +- 21 files changed, 874 insertions(+), 495 deletions(-) diff --git a/app/dialog/nodegroup/nodegroupdialog.cpp b/app/dialog/nodegroup/nodegroupdialog.cpp index 2d07a4338..9fd1fa4af 100644 --- a/app/dialog/nodegroup/nodegroupdialog.cpp +++ b/app/dialog/nodegroup/nodegroupdialog.cpp @@ -60,6 +60,8 @@ NodeGroupDialog::NodeGroupDialog(NodeGroup *group, QWidget *parent) : QMetaObject::invokeMethod(node_view, &NodeView::CenterOnItemsBoundingRect, Qt::QueuedConnection); splitter->addWidget(node_view); + splitter->setSizes({splitter->width() / 4, splitter->width() / 4 * 3}); + for (auto it=group->GetInputPassthroughs().cbegin(); it!=group->GetInputPassthroughs().cend(); it++) { param_view->SetInputChecked(it.value(), true); } diff --git a/app/node/graph.cpp b/app/node/graph.cpp index bdae43570..024cebf98 100644 --- a/app/node/graph.cpp +++ b/app/node/graph.cpp @@ -74,6 +74,12 @@ void NodeGraph::childEvent(QChildEvent *event) connect(node, &Node::ValueChanged, this, &NodeGraph::ValueChanged, Qt::DirectConnection); connect(node, &Node::InputValueHintChanged, this, &NodeGraph::InputValueHintChanged, Qt::DirectConnection); + if (NodeGroup *group = dynamic_cast(node)) { + connect(group, &NodeGroup::InputPassthroughAdded, this, &NodeGraph::GroupAddedInputPassthrough, Qt::DirectConnection); + connect(group, &NodeGroup::InputPassthroughRemoved, this, &NodeGraph::GroupRemovedInputPassthrough, Qt::DirectConnection); + connect(group, &NodeGroup::OutputPassthroughChanged, this, &NodeGraph::GroupChangedOutputPassthrough, Qt::DirectConnection); + } + emit NodeAdded(node); emit node->AddedToGraph(this); @@ -87,12 +93,22 @@ void NodeGraph::childEvent(QChildEvent *event) disconnect(node, &Node::ValueChanged, this, &NodeGraph::ValueChanged); disconnect(node, &Node::InputValueHintChanged, this, &NodeGraph::InputValueHintChanged); + if (NodeGroup *group = dynamic_cast(node)) { + disconnect(group, &NodeGroup::InputPassthroughAdded, this, &NodeGraph::GroupAddedInputPassthrough); + disconnect(group, &NodeGroup::InputPassthroughRemoved, this, &NodeGraph::GroupRemovedInputPassthrough); + disconnect(group, &NodeGroup::OutputPassthroughChanged, this, &NodeGraph::GroupChangedOutputPassthrough); + } + emit NodeRemoved(node); emit node->RemovedFromGraph(this); // Remove from any contexts foreach (Node *context, node_children_) { context->RemoveNodeFromContext(node); + + if (NodeGroup *group = dynamic_cast(context)) { + group->RemoveNode(node); + } } } } diff --git a/app/node/graph.h b/app/node/graph.h index fdf923b56..443e20748 100644 --- a/app/node/graph.h +++ b/app/node/graph.h @@ -21,6 +21,7 @@ #ifndef NODEGRAPH_H #define NODEGRAPH_H +#include "node/group/group.h" #include "node/node.h" namespace olive { @@ -84,9 +85,11 @@ signals: void InputValueHintChanged(const NodeInput& input); - void NodePositionAdded(Node *node, Node *relative, const QPointF &position); + void GroupAddedInputPassthrough(NodeGroup *group, const NodeInput &input); - void NodePositionRemoved(Node *node, Node *relative); + void GroupRemovedInputPassthrough(NodeGroup *group, const NodeInput &input); + + void GroupChangedOutputPassthrough(NodeGroup *group, Node *output); protected: void AddDefaultNode(Node* n) diff --git a/app/node/group/group.cpp b/app/node/group/group.cpp index fbe785bd9..f8e44790f 100644 --- a/app/node/group/group.cpp +++ b/app/node/group/group.cpp @@ -27,8 +27,6 @@ namespace olive { NodeGroup::NodeGroup() : output_passthrough_(nullptr) { - graph_ = new NodeGraph(); - graph_->setParent(this); } QString NodeGroup::Name() const @@ -57,26 +55,28 @@ QString NodeGroup::Description() const void NodeGroup::Retranslate() { - foreach (Node *n, graph_->nodes()) { + foreach (Node *n, nodes_) { n->Retranslate(); } } void NodeGroup::AddNode(Node *node) { - node->setParent(graph_); + nodes_.append(node); + + emit NodeAddedToGroup(node); } -void NodeGroup::RemoveNode(Node *node, QObject *new_parent) +void NodeGroup::RemoveNode(Node *node) { - if (node->parent() == graph_) { - node->setParent(new_parent); + if (nodes_.removeOne(node)) { + emit NodeRemovedFromGroup(node); } } void NodeGroup::AddInputPassthrough(const NodeInput &input) { - Q_ASSERT(graph_->nodes().contains(input.node())); + Q_ASSERT(nodes_.contains(input.node())); for (auto it=input_passthroughs_.cbegin(); it!=input_passthroughs_.cend(); it++) { if (it.value() == input) { @@ -91,6 +91,8 @@ void NodeGroup::AddInputPassthrough(const NodeInput &input) AddInput(id, input.GetDataType(), input.GetDefaultValue(), input.GetFlags()); input_passthroughs_.insert(id, input); + + emit InputPassthroughAdded(this, input); } void NodeGroup::RemoveInputPassthrough(const NodeInput &input) @@ -99,6 +101,7 @@ void NodeGroup::RemoveInputPassthrough(const NodeInput &input) if (it.value() == input) { RemoveInput(it.key()); input_passthroughs_.erase(it); + emit InputPassthroughRemoved(this, it.value()); break; } } @@ -106,9 +109,11 @@ void NodeGroup::RemoveInputPassthrough(const NodeInput &input) void NodeGroup::SetOutputPassthrough(Node *node) { - Q_ASSERT(graph_->nodes().contains(node)); + Q_ASSERT(!node || nodes_.contains(node)); output_passthrough_ = node; + + emit OutputPassthroughChanged(this, output_passthrough_); } QString NodeGroup::GetGroupInputIDFromInput(const NodeInput &input) @@ -135,15 +140,19 @@ bool NodeGroup::ContainsInputPassthrough(const NodeInput &input) const return false; } +QString NodeGroup::GetInputName(const QString &id) const +{ + return input_passthroughs_.value(id).name(); +} + void NodeAddToGroupCommand::redo() { - previous_parent_ = node_->parent(); group_->AddNode(node_); } void NodeAddToGroupCommand::undo() { - group_->RemoveNode(node_, previous_parent_); + group_->RemoveNode(node_); } void NodeGroupSetCustomNameCommand::redo() @@ -174,4 +183,25 @@ void NodeGroupAddInputPassthrough::undo() } } +void NodeRemoveFromGroupCommand::redo() +{ + group_->RemoveNode(node_); +} + +void NodeRemoveFromGroupCommand::undo() +{ + group_->AddNode(node_); +} + +void NodeGroupSetOutputPassthrough::redo() +{ + old_output_ = group_->GetOutputPassthrough(); + group_->SetOutputPassthrough(new_output_); +} + +void NodeGroupSetOutputPassthrough::undo() +{ + group_->SetOutputPassthrough(old_output_); +} + } diff --git a/app/node/group/group.h b/app/node/group/group.h index a0aa9cee3..31e07ab07 100644 --- a/app/node/group/group.h +++ b/app/node/group/group.h @@ -43,12 +43,27 @@ public: void AddNode(Node *node); - void RemoveNode(Node *node, QObject *new_parent = nullptr); + void RemoveNode(Node *node); + + bool ContainsNode(Node *node) const + { + return nodes_.contains(node); + } + + const QVector &GetNodes() const + { + return nodes_; + } void AddInputPassthrough(const NodeInput &input); void RemoveInputPassthrough(const NodeInput &input); + Node *GetOutputPassthrough() const + { + return output_passthrough_; + } + void SetOutputPassthrough(Node *node); const QString &GetCustomName() const @@ -78,8 +93,21 @@ public: bool ContainsInputPassthrough(const NodeInput &input) const; + virtual QString GetInputName(const QString& id) const override; + +signals: + void NodeAddedToGroup(Node *node); + + void NodeRemovedFromGroup(Node *node); + + void InputPassthroughAdded(NodeGroup *group, const NodeInput &input); + + void InputPassthroughRemoved(NodeGroup *group, const NodeInput &input); + + void OutputPassthroughChanged(NodeGroup *group, Node *output); + private: - NodeGraph *graph_; + QVector nodes_; QHash input_passthroughs_; @@ -112,7 +140,30 @@ private: NodeGroup *group_; - QObject *previous_parent_; +}; + +class NodeRemoveFromGroupCommand : public UndoCommand +{ +public: + NodeRemoveFromGroupCommand(Node *node, NodeGroup *group) : + node_(node), + group_(group) + {} + + virtual Project * GetRelevantProject() const override + { + return node_->project(); + } + +protected: + virtual void redo() override; + + virtual void undo() override; + +private: + Node *node_; + + NodeGroup *group_; }; @@ -171,6 +222,32 @@ private: }; +class NodeGroupSetOutputPassthrough : public UndoCommand +{ +public: + NodeGroupSetOutputPassthrough(NodeGroup *group, Node *output) : + group_(group), + new_output_(output) + {} + + virtual Project * GetRelevantProject() const override + { + return group_->project(); + } + +protected: + virtual void redo() override; + + virtual void undo() override; + +private: + NodeGroup *group_; + + Node *new_output_; + Node *old_output_; + +}; + } #endif // NODEGROUP_H diff --git a/app/node/node.cpp b/app/node/node.cpp index 1d0316e8b..4e850e821 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -1610,7 +1610,9 @@ void Node::CopyValuesOfElement(const Node *src, Node *dst, const QString &input, dst->SetSplitStandardValue(input, src->GetSplitStandardValue(input, src_element), dst_element); // Copy keyframes - dst->GetImmediate(input, dst_element)->delete_all_keyframes(); + if (NodeInputImmediate *immediate = dst->GetImmediate(input, dst_element)) { + immediate->delete_all_keyframes(); + } foreach (const NodeKeyframeTrack& track, src->GetImmediate(input, src_element)->keyframe_tracks()) { foreach (NodeKeyframe* key, track) { key->copy(dst_element, dst); @@ -2366,15 +2368,13 @@ Project *Node::ArrayResizeCommand::GetRelevantProject() const void NodeSetPositionCommand::redo() { - if (!(added_ = !context_->ContextContainsNode(node_))) { + added_ = !context_->ContextContainsNode(node_); + + if (!added_) { old_pos_ = context_->GetNodePositionDataInContext(node_); } - if (added_) { - context_->SetNodePositionInContext(node_, pos_); - } else { - move(context_, node_, pos_.position - old_pos_.position, move_deps_); - } + context_->SetNodePositionInContext(node_, pos_); } void NodeSetPositionCommand::undo() @@ -2382,23 +2382,7 @@ void NodeSetPositionCommand::undo() if (added_) { context_->RemoveNodeFromContext(node_); } else { - move(context_, node_, old_pos_.position - pos_.position, move_deps_); - } -} - -void NodeSetPositionCommand::move(Node *context, Node *node, const QPointF &diff, bool recursive) -{ - QPointF p = context->GetNodePositionInContext(node); - p += diff; - context->SetNodePositionInContext(node, p); - - if (recursive) { - for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { - Node *output = it->second; - if (context->ContextContainsNode(output)) { - move(context, output, diff, recursive); - } - } + context_->SetNodePositionInContext(node_, old_pos_); } } @@ -2407,7 +2391,7 @@ void NodeRemovePositionFromContextCommand::redo() contained_ = context_->ContextContainsNode(node_); if (contained_) { - old_pos_ = context_->GetNodePositionInContext(node_); + old_pos_ = context_->GetNodePositionDataInContext(node_); context_->RemoveNodeFromContext(node_); } } @@ -2486,4 +2470,37 @@ void Node::ValueHint::Save(QXmlStreamWriter *writer) const writer->writeTextElement(QStringLiteral("tag"), tag_); } +void NodeSetPositionAndDependenciesRecursivelyCommand::prepare() +{ + move_recursively(node_, pos_.position - context_->GetNodePositionDataInContext(node_).position); +} + +void NodeSetPositionAndDependenciesRecursivelyCommand::redo() +{ + for (auto it=commands_.cbegin(); it!=commands_.cend(); it++) { + (*it)->redo_now(); + } +} + +void NodeSetPositionAndDependenciesRecursivelyCommand::undo() +{ + for (auto it=commands_.crbegin(); it!=commands_.crend(); it++) { + (*it)->undo_now(); + } +} + +void NodeSetPositionAndDependenciesRecursivelyCommand::move_recursively(Node *node, const QPointF &diff) +{ + Node::Position pos = context_->GetNodePositionDataInContext(node); + pos += diff; + commands_.append(new NodeSetPositionCommand(node_, context_, pos)); + + for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { + Node *output = it->second; + if (context_->ContextContainsNode(output)) { + move_recursively(output, diff); + } + } +} + } diff --git a/app/node/node.h b/app/node/node.h index 3a0fa5130..f38835ce9 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -320,7 +320,7 @@ public: static void DisconnectEdge(Node *output, const NodeInput& input); - QString GetInputName(const QString& id) const; + virtual QString GetInputName(const QString& id) const; void LoadInput(QXmlStreamReader* reader, XMLNodeData &xml_node_data, const QAtomicInt *cancelled); void SaveInput(QXmlStreamWriter* writer, const QString& id) const; @@ -1420,12 +1420,11 @@ using NodePtr = std::shared_ptr; class NodeSetPositionCommand : public UndoCommand { public: - NodeSetPositionCommand(Node* node, Node* context, const Node::Position& pos, bool move_dependencies_relatively = false) + NodeSetPositionCommand(Node* node, Node* context, const Node::Position& pos) { node_ = node; context_ = context; pos_ = pos; - move_deps_ = move_dependencies_relatively; } virtual Project* GetRelevantProject() const override @@ -1439,14 +1438,41 @@ protected: virtual void undo() override; private: - static void move(Node *context, Node *node, const QPointF &diff, bool recursive); - Node* node_; Node* context_; Node::Position pos_; Node::Position old_pos_; bool added_; - bool move_deps_; + +}; + +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_; }; @@ -1474,7 +1500,7 @@ private: Node *context_; - QPointF old_pos_; + Node::Position old_pos_; bool contained_; diff --git a/app/node/param.cpp b/app/node/param.cpp index 2e579f613..d7676c615 100644 --- a/app/node/param.cpp +++ b/app/node/param.cpp @@ -150,6 +150,15 @@ QVariant NodeInput::GetSplitDefaultValueForTrack(int track) const } } +int NodeInput::GetArraySize() const +{ + if (IsValid() && element_ == -1) { + return node_->InputArraySize(input_); + } else { + return 0; + } +} + uint qHash(const NodeInput &i) { return qHash(i.node()) ^ qHash(i.input()) ^ qHash(i.element()); diff --git a/app/node/param.h b/app/node/param.h index 2e2276187..77f8e63f6 100644 --- a/app/node/param.h +++ b/app/node/param.h @@ -134,6 +134,16 @@ public: return element_; } + void set_node(Node *node) + { + node_ = node; + } + + void set_input(const QString &input) + { + input_ = input; + } + void set_element(int e) { element_ = e; @@ -172,6 +182,8 @@ public: QVariant GetSplitDefaultValueForTrack(int track) const; + int GetArraySize() const; + void Reset() { *this = NodeInput(); diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 9b3619e0e..2dab4cb29 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -49,10 +49,9 @@ NodeView::NodeView(QWidget *parent) : create_edge_(nullptr), create_edge_output_item_(nullptr), create_edge_input_item_(nullptr), - create_edge_dst_temp_expanded_(false), + create_edge_expand_item_(nullptr), paste_command_(nullptr), - scale_(1.0), - first_show_(true) + scale_(1.0) { setScene(&scene_); SetDefaultDragMode(RubberBandDrag); @@ -311,46 +310,55 @@ void NodeView::mousePressEvent(QMouseEvent *event) QGraphicsItem* item = itemAt(event->pos()); if (event->button() == Qt::LeftButton) { - // Determine if user clicked on a connector - NodeViewItemConnector *connector = dynamic_cast(item); + // Sane defaults + create_edge_output_item_ = nullptr; + create_edge_input_item_ = nullptr; + create_edge_already_exists_ = false; + create_edge_from_output_ = true; - // If the user clicked on a connector OR the user is holding Ctrl - if (connector || (event->modifiers() & Qt::ControlModifier)) { - // Get the relevant item, either the one attached to the connector or the item if Ctrl+Clicked - NodeViewItem *attached_item = connector ? static_cast(connector->parentItem()) : dynamic_cast(item); - - if (attached_item) { - if (connector && !connector->IsOutput() && (create_edge_ = attached_item->GetEdgeFromInputConnector(connector))) { - // Since inputs can only have one edge connected, we grab the existing edge, if one exists - create_edge_output_item_ = create_edge_->from_item(); - create_edge_already_exists_ = true; - create_edge_from_output_ = true; - } else { - // Create a new edge from this output - create_edge_ = new NodeViewEdge(); - create_edge_->SetCurved(scene_.GetEdgesAreCurved()); - create_edge_->SetFlowDirection(scene_.GetFlowDirection()); - - // Set source and declare that we created this edge - if ((create_edge_from_output_ = (!connector || connector->IsOutput()))) { - // Edge is being created from output - create_edge_output_item_ = attached_item; - } else { - // Edge is being created from input - create_edge_input_item_ = attached_item; - create_edge_input_ = attached_item->GetInputFromInputConnector(connector); - } - create_edge_already_exists_ = false; - - // Add edge to scene - scene_.addItem(create_edge_); - - // Position edge to mouse cursor - PositionNewEdge(event->pos()); - } - return; + if (event->modifiers() & Qt::ControlModifier) { + create_edge_output_item_ = dynamic_cast(item); + if (create_edge_output_item_ && !create_edge_output_item_->IsOutputItem()) { + create_edge_output_item_ = nullptr; } } + + if (!create_edge_output_item_) { + // Determine if user clicked on a connector + if (NodeViewItemConnector *connector = dynamic_cast(item)) { + NodeViewItem *attached = static_cast(connector->parentItem()); + + if (connector->IsOutput()) { + create_edge_output_item_ = attached; + } else { + create_edge_input_item_ = attached; + + if (!create_edge_input_item_->edges().isEmpty()) { + // Drag existing edge instead + create_edge_ = create_edge_input_item_->edges().first(); + create_edge_input_item_ = nullptr; + create_edge_output_item_ = create_edge_->from_item(); + create_edge_already_exists_ = true; + } else { + create_edge_from_output_ = false; + create_edge_input_ = create_edge_input_item_->GetInput(); + } + } + } + } + + if ((create_edge_output_item_ || create_edge_input_item_) && !create_edge_already_exists_) { + // Create a new edge from this output + create_edge_ = new NodeViewEdge(); + create_edge_->SetCurved(scene_.GetEdgesAreCurved()); + + // Add edge to scene + scene_.addItem(create_edge_); + + // Position edge to mouse cursor + PositionNewEdge(event->pos()); + return; + } } // Handle selections with the right mouse button @@ -490,12 +498,12 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) if (create_edge_output_item_ && create_edge_input_item_) { // Clear highlight if we set one - create_edge_input_item_->SetHighlightedIndex(-1); + create_edge_input_item_->SetHighlighted(false); // Collapse if we expanded it - if (create_edge_dst_temp_expanded_) { - create_edge_input_item_->SetExpanded(false); - create_edge_input_item_->setZValue(0); + if (create_edge_expand_item_) { + create_edge_expand_item_->SetExpanded(false); + create_edge_expand_item_->setZValue(0); } NodeInput &creating_input = create_edge_input_; @@ -504,17 +512,20 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) if (!reconnected_to_itself) { Node *creating_output = create_edge_output_item_->GetNode(); + if (NodeGroup *output_group = dynamic_cast(creating_output)) { + creating_output = output_group->GetOutputPassthrough(); + } + + if (NodeGroup *input_group = dynamic_cast(creating_input.node())) { + creating_input = input_group->GetInputPassthroughs().value(creating_input.input()); + } + 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)); } command->add_child(new NodeEdgeAddCommand(creating_output, creating_input)); - - // If the output is not in the input's context, add it now - if (!create_edge_input_item_->GetContext()->ContextContainsNode(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(); @@ -686,9 +697,7 @@ void NodeView::ShowContextMenu(const QPoint &pos) // Label node action QAction* label_action = m.addAction(tr("Label")); - connect(label_action, &QAction::triggered, this, [this](){ - Core::instance()->LabelNodes(selected_nodes_); - }); + connect(label_action, &QAction::triggered, this, &NodeView::LabelSelectedNodes); // Grouping if (selected.size() == 1 && dynamic_cast(selected.first()->GetNode())) { @@ -702,13 +711,19 @@ void NodeView::ShowContextMenu(const QPoint &pos) // Color menu MenuShared::instance()->AddColorCodingMenu(&m); - ViewerOutput* viewer = dynamic_cast(selected.first()->GetNode()); - if (viewer) { + // Show in Viewer option for nodes based on Viewer + if (ViewerOutput* viewer = dynamic_cast(selected.first()->GetNode())) { m.addSeparator(); QAction* open_in_viewer_action = m.addAction(tr("Open in Viewer")); connect(open_in_viewer_action, &QAction::triggered, this, &NodeView::OpenSelectedNodeInViewer); } + m.addSeparator(); + + // Properties + QAction *properties_action = m.addAction(tr("P&roperties")); + connect(properties_action, &QAction::triggered, this, &NodeView::ShowNodeProperties); + } else { QAction* curved_action = m.addAction(tr("Smooth Edges")); @@ -988,6 +1003,13 @@ void NodeView::PasteNodesFromClipboardInternal(QXmlStreamReader *reader, XMLNode }*/ } +void NodeView::changeEvent(QEvent *e) +{ + // Add translation code + + super::changeEvent(e); +} + void NodeView::ZoomFromKeyboard(double multiplier) { QPoint cursor_pos = mapFromGlobal(QCursor::pos()); @@ -1029,10 +1051,11 @@ void NodeView::PositionNewEdge(const QPoint &pos) item_at_cursor = nullptr; } - // Filter out connecting to a node that connects to us + // Filter out connecting to a node that connects to us or an item of the same type if (item_at_cursor && ((create_edge_from_output_ && item_at_cursor->GetNode()->OutputsTo(source_item->GetNode(), true)) - || (!create_edge_from_output_ && item_at_cursor->GetNode()->InputsFrom(source_item->GetNode(), true)))) { + || (!create_edge_from_output_ && item_at_cursor->GetNode()->InputsFrom(source_item->GetNode(), true)) + || (create_edge_from_output_ == item_at_cursor->IsOutputItem()))) { item_at_cursor = nullptr; } @@ -1040,46 +1063,32 @@ void NodeView::PositionNewEdge(const QPoint &pos) if (item_at_cursor != opposing_item) { // If we had a destination active, disconnect from it since the item has changed if (opposing_item) { - opposing_item->SetHighlightedIndex(-1); + opposing_item->SetHighlighted(false); + opposing_item = nullptr; + } - if (create_edge_dst_temp_expanded_) { - // We expanded this item, so we can un-expand it - opposing_item->SetExpanded(false); - opposing_item->setZValue(0); + // Clear cached input + if (create_edge_from_output_) { + if (create_edge_input_.IsValid()) { + create_edge_input_.Reset(); } } - // Set destination + // If this is an input and we're opposing_item = item_at_cursor; - // If our destination is an item, ensure it's expanded if (opposing_item) { - if (create_edge_from_output_ && (create_edge_dst_temp_expanded_ = (!create_edge_input_item_->IsExpanded()))) { - create_edge_input_item_->SetExpanded(true, true); - create_edge_input_item_->setZValue(100); // Ensure item is in front + opposing_item->SetHighlighted(true); + if (!opposing_item->IsOutputItem()) { + create_edge_input_ = opposing_item->GetInput(); } } } - // If we have a destination, highlight the appropriate input - if (create_edge_from_output_) { - int highlight_index = -1; - if (create_edge_input_item_) { - highlight_index = create_edge_input_item_->GetIndexAt(scene_pt); - create_edge_input_item_->SetHighlightedIndex(highlight_index); - } - - if (highlight_index >= 0) { - create_edge_input_ = create_edge_input_item_->GetInputAtIndex(highlight_index); - } else { - create_edge_input_.Reset(); - } - } - QPointF output_point = create_edge_output_item_ ? create_edge_output_item_->GetOutputPoint() : scene_pt; - QPointF input_point = create_edge_input_.IsValid() ? create_edge_input_item_->GetInputPoint(create_edge_input_.input(), create_edge_input_.element()) : scene_pt; + QPointF input_point = create_edge_input_.IsValid() ? create_edge_input_item_->GetInputPoint() : scene_pt; - create_edge_->SetPoints(output_point, input_point, create_edge_input_item_ && create_edge_input_item_->IsExpanded()); + create_edge_->SetPoints(output_point, input_point); create_edge_->SetConnected(create_edge_output_item_ && create_edge_input_.IsValid()); } @@ -1110,36 +1119,14 @@ void NodeView::GroupNodes() // Add group to graph and context MultiUndoCommand *command = new MultiUndoCommand(); - command->add_child(new NodeAddCommand(context->parent(), group)); - command->add_child(new NodeSetPositionCommand(group, context, avg_pos)); - // Add nodes to group + Node *output_passthrough = nullptr; QVector nodes_to_group = selected_nodes_; DeselectAll(); foreach (Node *n, nodes_to_group) { - for (auto it=n->input_connections().cbegin(); it!=n->input_connections().cend(); it++) { - Node *output = it->second; - const NodeInput &input = it->first; - - if (!nodes_to_group.contains(output)) { - command->add_child(new NodeEdgeRemoveCommand(output, input)); - command->add_child(new NodeEdgeAddCommand(output, NodeInput(group, input.input(), input.element()))); - } - } - - for (auto it=n->output_connections().cbegin(); it!=n->output_connections().cend(); it++) { - Node *output = it->first; - const NodeInput &input = it->second; - - if (!nodes_to_group.contains(input.node())) { - command->add_child(new NodeEdgeRemoveCommand(output, input)); - command->add_child(new NodeEdgeAddCommand(group, input)); - } - } - command->add_child(new NodeRemovePositionFromContextCommand(n, context)); command->add_child(new NodeAddToGroupCommand(n, group)); - command->add_child(new NodeSetPositionCommand(n, group, scene_.context_map().value(context)->GetItemFromMap(n)->GetNodePosition())); + command->add_child(new NodeSetPositionCommand(n, group, context->GetNodePositionDataInContext(n))); for (auto it=n->inputs().cbegin(); it!=n->inputs().cend(); it++) { NodeInput input(n, *it, -1); @@ -1148,8 +1135,25 @@ void NodeView::GroupNodes() command->add_child(new NodeGroupAddInputPassthrough(group, input)); } } + + if (!output_passthrough) { + // Default to the first node we find that doesn't output to a node inside the group + foreach (Node *potential_in, nodes_to_group) { + if (potential_in != n && !n->OutputsTo(potential_in, false)) { + output_passthrough = n; + break; + } + } + } } + // Set output passthrough + command->add_child(new NodeGroupSetOutputPassthrough(group, output_passthrough)); + + // Add group to graph + command->add_child(new NodeAddCommand(context->parent(), group)); + command->add_child(new NodeSetPositionCommand(group, context, avg_pos)); + // Do command command->redo_now(); @@ -1166,7 +1170,55 @@ void NodeView::GroupNodes() void NodeView::UngroupNodes() { - //NodeGroup *group = static_cast(selected_nodes_.first()); + NodeViewItem *group_item = nullptr; + QVector items = scene_.GetSelectedItems(); + if (items.isEmpty()) { + return; + } + + NodeGroup *group; + foreach (NodeViewItem *i, items) { + if ((group = dynamic_cast(i->GetNode()))) { + group_item = i; + break; + } + } + + if (!group_item) { + return; + } + + MultiUndoCommand *command = new MultiUndoCommand(); + + Node *context = group_item->GetContext(); + + command->add_child(new NodeRemovePositionFromContextCommand(group, context)); + command->add_child(new NodeRemoveAndDisconnectCommand(group)); + + foreach (Node *n, group->GetNodes()) { + command->add_child(new NodeRemovePositionFromContextCommand(n, group)); + command->add_child(new NodeRemoveFromGroupCommand(n, group)); + command->add_child(new NodeSetPositionCommand(n, context, group->GetNodePositionDataInContext(n))); + } + + Core::instance()->undo_stack()->push(command); +} + +void NodeView::ShowNodeProperties() +{ + Node *first_node = selected_nodes_.first(); + + if (NodeGroup *group = dynamic_cast(first_node)) { + NodeGroupDialog ngd(group, this); + ngd.exec(); + } else { + LabelSelectedNodes(); + } +} + +void NodeView::LabelSelectedNodes() +{ + Core::instance()->LabelNodes(selected_nodes_); } void NodeView::PasteNodesInternal(const QVector &duplicate_nodes) diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index 5f7c802bb..4b3a0417f 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -119,6 +119,8 @@ protected: virtual void CopyNodesToClipboardInternal(QXmlStreamWriter *writer, const QVector &nodes, void* userdata) override; virtual void PasteNodesFromClipboardInternal(QXmlStreamReader *reader, XMLNodeData &xml_node_data, void* userdata) override; + virtual void changeEvent(QEvent *e) override; + private: void AttachItemsToCursor(const QVector &items); @@ -162,8 +164,8 @@ private: NodeViewEdge* create_edge_; NodeViewItem* create_edge_output_item_; NodeViewItem* create_edge_input_item_; + NodeViewItem *create_edge_expand_item_; NodeInput create_edge_input_; - bool create_edge_dst_temp_expanded_; bool create_edge_already_exists_; bool create_edge_from_output_; @@ -181,8 +183,6 @@ private: double scale_; - bool first_show_; - static const double kMinimumScale; private slots: @@ -225,6 +225,10 @@ private slots: void UngroupNodes(); + void ShowNodeProperties(); + + void LabelSelectedNodes(); + }; } diff --git a/app/widget/nodeview/nodeviewcommon.h b/app/widget/nodeview/nodeviewcommon.h index d0541fba4..f8ffa4583 100644 --- a/app/widget/nodeview/nodeviewcommon.h +++ b/app/widget/nodeview/nodeviewcommon.h @@ -30,6 +30,7 @@ namespace olive { class NodeViewCommon { public: enum FlowDirection { + kInvalidDirection = -1, kTopToBottom, kBottomToTop, kLeftToRight, diff --git a/app/widget/nodeview/nodeviewcontext.cpp b/app/widget/nodeview/nodeviewcontext.cpp index c2c516c60..59c640634 100644 --- a/app/widget/nodeview/nodeviewcontext.cpp +++ b/app/widget/nodeview/nodeviewcontext.cpp @@ -53,29 +53,16 @@ void NodeViewContext::AddChild(Node *node) NodeViewItem *item = new NodeViewItem(node, context_, this); item->SetFlowDirection(flow_dir_); - connect(node, &Node::InputConnected, this, &NodeViewContext::ChildInputConnected); - connect(node, &Node::InputDisconnected, this, &NodeViewContext::ChildInputDisconnected); + AddNodeInternal(node, item); - item_map_.insert(node, item); - - if (node == context_) { - item->SetLabelAsOutput(true); - } - - for (auto it=node->output_connections().cbegin(); it!=node->output_connections().cend(); it++) { - if (!it->second.IsHidden()) { - if (NodeViewItem *other_item = item_map_.value(it->second.node())) { - AddEdgeInternal(node, it->second, item, other_item); - } + if (NodeGroup *group = dynamic_cast(node)) { + foreach (Node *n, group->GetNodes()) { + // Use this item as the representative for all of these nodes too + AddNodeInternal(n, item); } - } - for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { - if (!it->first.IsHidden()) { - if (NodeViewItem *other_item = item_map_.value(it->second)) { - AddEdgeInternal(it->second, it->first, other_item, item); - } - } + connect(group, &NodeGroup::NodeAddedToGroup, this, &NodeViewContext::GroupAddedNode); + connect(group, &NodeGroup::NodeRemovedFromGroup, this, &NodeViewContext::GroupRemovedNode); } UpdateRect(); @@ -95,12 +82,28 @@ void NodeViewContext::RemoveChild(Node *node) // Delete edges first because the edge destructor will try to reference item (maybe that should // be changed...) - QVector edges_to_remove = item->edges(); + QVector edges_to_remove = item->GetAllEdgesRecursively(); foreach (NodeViewEdge *edge, edges_to_remove) { - ChildInputDisconnected(edge->output(), edge->input()); + if (node == item->GetNode() || edge->output() == node || edge->input().node() == node) { + ChildInputDisconnected(edge->output(), edge->input()); + } } - delete item; + // Check if this item is specifically for this node and the node is a group. If so, remove it for + // all other entries in the map. + if (item->GetNode() == node) { + if (dynamic_cast(item->GetNode())) { + for (auto it=item_map_.begin(); it!=item_map_.end(); ) { + if (it.value() == item) { + it = item_map_.erase(it); + } else { + it++; + } + } + } + + delete item; + } } void NodeViewContext::ChildInputConnected(Node *output, const NodeInput &input) @@ -108,7 +111,7 @@ void NodeViewContext::ChildInputConnected(Node *output, const NodeInput &input) // Add edge if (!input.IsHidden()) { if (NodeViewItem* output_item = item_map_.value(output)) { - AddEdgeInternal(output, input, output_item, item_map_.value(input.node())); + AddEdgeInternal(output, input, output_item, item_map_.value(input.node())->GetItemForInput(input)); } } } @@ -117,8 +120,9 @@ bool NodeViewContext::ChildInputDisconnected(Node *output, const NodeInput &inpu { // Remove edge for (int i=0; ioutput() == output && edges_.at(i)->input() == input) { - delete edges_.at(i); + NodeViewEdge *e = edges_.at(i); + if (e->output() == output && e->input() == input) { + delete e; edges_.removeAt(i); return true; } @@ -154,10 +158,6 @@ void NodeViewContext::SetFlowDirection(NodeViewCommon::FlowDirection dir) foreach (NodeViewItem *item, item_map_) { item->SetFlowDirection(dir); } - - foreach (NodeViewEdge *edge, edges_) { - edge->SetFlowDirection(dir); - } } void NodeViewContext::SetCurvedEdges(bool e) @@ -201,7 +201,9 @@ QVector NodeViewContext::GetSelectedItems() const for (auto it=item_map_.cbegin(); it!=item_map_.cend(); it++) { if (it.value()->isSelected()) { - items.append(it.value()); + if (!items.contains(it.value())) { + items.append(it.value()); + } } } @@ -269,16 +271,62 @@ void NodeViewContext::mousePressEvent(QGraphicsSceneMouseEvent *event) super::mousePressEvent(event); } -NodeViewEdge* NodeViewContext::AddEdgeInternal(Node *output, const NodeInput& input, NodeViewItem *from, NodeViewItem *to) +void NodeViewContext::AddNodeInternal(Node *node, NodeViewItem *item) { + connect(node, &Node::InputConnected, this, &NodeViewContext::ChildInputConnected); + connect(node, &Node::InputDisconnected, this, &NodeViewContext::ChildInputDisconnected); + + item_map_.insert(node, item); + + if (node == context_) { + item->SetLabelAsOutput(true); + } + + for (auto it=node->output_connections().cbegin(); it!=node->output_connections().cend(); it++) { + if (!it->second.IsHidden()) { + if (NodeViewItem *other_item = item_map_.value(it->second.node())) { + AddEdgeInternal(node, it->second, item, other_item->GetItemForInput(it->second)); + } + } + } + + for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { + if (!it->first.IsHidden()) { + if (NodeViewItem *other_item = item_map_.value(it->second)) { + AddEdgeInternal(it->second, it->first, other_item, item->GetItemForInput(it->first)); + } + } + } +} + +void NodeViewContext::AddEdgeInternal(Node *output, const NodeInput& input, NodeViewItem *from, NodeViewItem *to) +{ + if (from == to) { + return; + } + NodeViewEdge* edge_ui = new NodeViewEdge(output, input, from, to, this); - edge_ui->SetFlowDirection(flow_dir_); + edge_ui->Adjust(); edge_ui->SetCurved(curved_edges_); edges_.append(edge_ui); +} - return edge_ui; +void NodeViewContext::GroupAddedNode(Node *node) +{ + NodeGroup *group = static_cast(sender()); + + AddNodeInternal(node, item_map_.value(group)); +} + +void NodeViewContext::GroupRemovedNode(Node *node) +{ + NodeGroup *group = static_cast(sender()); + + if (item_map_.value(node) == item_map_.value(group)) { + item_map_.remove(node); + } } } diff --git a/app/widget/nodeview/nodeviewcontext.h b/app/widget/nodeview/nodeviewcontext.h index 7cdc638a7..6d191d916 100644 --- a/app/widget/nodeview/nodeviewcontext.h +++ b/app/widget/nodeview/nodeviewcontext.h @@ -60,7 +60,9 @@ protected: virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override; private: - NodeViewEdge *AddEdgeInternal(Node *output, const NodeInput& input, NodeViewItem *from, NodeViewItem *to); + void AddNodeInternal(Node *node, NodeViewItem *item); + + void AddEdgeInternal(Node *output, const NodeInput& input, NodeViewItem *from, NodeViewItem *to); Node *context_; @@ -76,6 +78,11 @@ private: QVector edges_; +private slots: + void GroupAddedNode(Node *node); + + void GroupRemovedNode(Node *node); + }; } diff --git a/app/widget/nodeview/nodeviewedge.cpp b/app/widget/nodeview/nodeviewedge.cpp index 794c98c52..0cf408ea1 100644 --- a/app/widget/nodeview/nodeviewedge.cpp +++ b/app/widget/nodeview/nodeviewedge.cpp @@ -70,12 +70,40 @@ NodeViewEdge::~NodeViewEdge() } } +void NodeViewEdge::set_from_item(NodeViewItem *i) +{ + if (from_item_) { + from_item_->RemoveEdge(this); + } + + from_item_ = i; + + if (from_item_) { + from_item_->AddEdge(this); + } + + Adjust(); +} + +void NodeViewEdge::set_to_item(NodeViewItem *i) +{ + if (to_item_) { + to_item_->RemoveEdge(this); + } + + to_item_ = i; + + if (to_item_) { + to_item_->AddEdge(this); + } + + Adjust(); +} + void NodeViewEdge::Adjust() { // Draw a line between the two - SetPoints(from_item()->GetOutputPoint(), - to_item()->GetInputPoint(input_.input(), input_.element()), - to_item()->IsExpanded()); + SetPoints(from_item()->GetOutputPoint(), to_item()->GetInputPoint()); } void NodeViewEdge::SetConnected(bool c) @@ -92,24 +120,14 @@ void NodeViewEdge::SetHighlighted(bool e) update(); } -void NodeViewEdge::SetPoints(const QPointF &start, const QPointF &end, bool input_is_expanded) +void NodeViewEdge::SetPoints(const QPointF &start, const QPointF &end) { cached_start_ = start; cached_end_ = end; - cached_input_is_expanded_ = input_is_expanded; UpdateCurve(); } -void NodeViewEdge::SetFlowDirection(NodeViewCommon::FlowDirection dir) -{ - flow_dir_ = dir; - - if (from_item_ && to_item_) { - Adjust(); - } -} - void NodeViewEdge::SetCurved(bool e) { curved_ = e; @@ -146,7 +164,6 @@ void NodeViewEdge::Init() { connected_ = false; highlighted_ = false; - flow_dir_ = NodeViewCommon::kLeftToRight; curved_ = true; setFlag(QGraphicsItem::ItemIsSelectable); @@ -175,13 +192,26 @@ void NodeViewEdge::UpdateCurve() QPointF cp1, cp2; - if (NodeViewCommon::GetFlowOrientation(flow_dir_) == Qt::Horizontal) { + NodeViewCommon::FlowDirection from_flow = from_item_ ? from_item_->GetFlowDirection() : NodeViewCommon::kInvalidDirection; + NodeViewCommon::FlowDirection to_flow = to_item_ ? to_item_->GetFlowDirection() : NodeViewCommon::kInvalidDirection; + + if (from_flow == NodeViewCommon::kInvalidDirection && to_flow == NodeViewCommon::kInvalidDirection) { + // This is a technically unsupported scenario, but to avoid issues, we'll use a fallback + from_flow = NodeViewCommon::kLeftToRight; + to_flow = NodeViewCommon::kLeftToRight; + } else if (from_flow == NodeViewCommon::kInvalidDirection) { + from_flow = to_flow; + } else if (to_flow == NodeViewCommon::kInvalidDirection) { + to_flow = from_flow; + } + + if (NodeViewCommon::GetFlowOrientation(from_flow) == Qt::Horizontal) { cp1 = QPointF(half_x, start.y()); } else { cp1 = QPointF(start.x(), half_y); } - if (NodeViewCommon::GetFlowOrientation(flow_dir_) == Qt::Horizontal || cached_input_is_expanded_) { + if (NodeViewCommon::GetFlowOrientation(to_flow) == Qt::Horizontal) { cp2 = QPointF(half_x, end.y()); } else { cp2 = QPointF(end.x(), half_y); diff --git a/app/widget/nodeview/nodeviewedge.h b/app/widget/nodeview/nodeviewedge.h index 31a56664e..b149b3680 100644 --- a/app/widget/nodeview/nodeviewedge.h +++ b/app/widget/nodeview/nodeviewedge.h @@ -72,6 +72,10 @@ public: return to_item_; } + void set_from_item(NodeViewItem *i); + + void set_to_item(NodeViewItem *i); + void Adjust(); /** @@ -101,12 +105,7 @@ public: /** * @brief Set points to create curve from */ - void SetPoints(const QPointF& start, const QPointF& end, bool input_is_expanded); - - /** - * @brief Sets the direction nodes are flowing - */ - void SetFlowDirection(NodeViewCommon::FlowDirection dir); + void SetPoints(const QPointF& start, const QPointF& end); /** * @brief Set whether edges should be drawn as curved or as straight lines @@ -137,13 +136,10 @@ private: bool highlighted_; - NodeViewCommon::FlowDirection flow_dir_; - bool curved_; QPointF cached_start_; QPointF cached_end_; - bool cached_input_is_expanded_; }; diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 1a9b6c516..9357a8f0b 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -39,21 +39,17 @@ namespace olive { -NodeViewItem::NodeViewItem(Node* n, Node *context, QGraphicsItem *parent) : +NodeViewItem::NodeViewItem(Node *node, const QString &input, int element, Node *context, QGraphicsItem *parent) : QGraphicsRectItem(parent), - node_(n), + node_(node), + input_(input), + element_(element), context_(context), expanded_(false), - hide_titlebar_(false), - highlighted_index_(-1), - flow_dir_(NodeViewCommon::kLeftToRight), + highlighted_(false), + flow_dir_(NodeViewCommon::kInvalidDirection), label_as_output_(false) { - // Set flags for this widget - setFlag(QGraphicsItem::ItemIsMovable); - setFlag(QGraphicsItem::ItemIsSelectable); - setFlag(QGraphicsItem::ItemSendsGeometryChanges); - // // We use font metrics to set all the UI measurements for DPI-awareness // @@ -61,32 +57,41 @@ NodeViewItem::NodeViewItem(Node* n, Node *context, QGraphicsItem *parent) : // Set border width node_border_width_ = DefaultItemBorder(); - int widget_width = DefaultItemWidth(); - int widget_height = DefaultItemHeight(); - - title_bar_rect_ = QRectF(-widget_width/2, -widget_height/2, widget_width, widget_height); - setRect(title_bar_rect_); + // Set rect size to default + SetRectSize(); + // Create connector + input_connector_ = new NodeViewItemConnector(false, this); output_connector_ = new NodeViewItemConnector(true, this); - // Set up node - node_->Retranslate(); - - foreach (const QString& input, node_->inputs()) { - if (node_->IsInputConnectable(input) && !node_->IsInputHidden(input)) { - node_inputs_.append(input); - } - } - - UpdateInputConnectors(); - connect(node_, &Node::LabelChanged, this, &NodeViewItem::NodeAppearanceChanged); connect(node_, &Node::ColorChanged, this, &NodeViewItem::NodeAppearanceChanged); - if (context_) { - SetNodePosition(context_->GetNodePositionInContext(node_)); - SetExpanded(context_->IsNodeExpandedInContext(node_)); + if (IsOutputItem()) { + connect(node_, &Node::InputAdded, this, &NodeViewItem::RepopulateInputs); + connect(node_, &Node::InputRemoved, this, &NodeViewItem::RepopulateInputs); + RepopulateInputs(); + + // Set flags for this widget + setFlag(QGraphicsItem::ItemSendsGeometryChanges); + setFlag(QGraphicsItem::ItemIsMovable); + setFlag(QGraphicsItem::ItemIsSelectable); + + if (context_) { + SetNodePosition(context_->GetNodePositionInContext(node_)); + SetExpanded(context_->IsNodeExpandedInContext(node_)); + } + } else { + output_connector_->setVisible(false); } + + // This should be set during runtime, but just in case here's a default fallback + SetFlowDirection(NodeViewCommon::kLeftToRight); +} + +NodeViewItem::~NodeViewItem() +{ + Q_ASSERT(edges_.isEmpty()); } QPointF NodeViewItem::GetNodePosition() const @@ -101,6 +106,17 @@ void NodeViewItem::SetNodePosition(const QPointF &pos) UpdateNodePosition(); } +QVector NodeViewItem::GetAllEdgesRecursively() const +{ + QVector list = edges_; + + foreach (NodeViewItem *item, children_) { + list.append(item->GetAllEdgesRecursively()); + } + + return list; +} + int NodeViewItem::DefaultTextPadding() { return QFontMetrics(QFont()).height() / 4; @@ -139,6 +155,8 @@ QPointF NodeViewItem::NodeToScreenPoint(QPointF p, NodeViewCommon::FlowDirection // Swap X/Y and invert Y p = QPointF(p.y(), -p.x()); break; + case NodeViewCommon::kInvalidDirection: + break; } // Multiply by item sizes for this direction @@ -164,12 +182,14 @@ QPointF NodeViewItem::ScreenToNodePoint(QPointF p, NodeViewCommon::FlowDirection break; case NodeViewCommon::kTopToBottom: // Swap X/Y - p = QPointF(p.y(), p.x()); + p = QPointF(p.y(), p.x()); break; case NodeViewCommon::kBottomToTop: // Swap X/Y and invert Y p = QPointF(-p.y(), p.x()); break; + case NodeViewCommon::kInvalidDirection: + break; } return p; @@ -213,51 +233,79 @@ void NodeViewItem::RemoveEdge(NodeViewEdge *edge) edges_.removeOne(edge); } -int NodeViewItem::GetIndexAt(QPointF pt) const -{ - pt -= this->scenePos(); - - for (int i=0; iGetInputFlags(input_) & kInputFlagArray)) + || (expanded_ == e)) { return; } expanded_ = e; - hide_titlebar_ = hide_titlebar; if (context_) { context_->SetNodeExpandedInContext(node_, e); } - if (expanded_ && !node_inputs_.isEmpty()) { - // Create new rect - QRectF new_rect = title_bar_rect_; - - if (hide_titlebar_) { - new_rect.setHeight(new_rect.height() * node_inputs_.size()); - } else { - new_rect.setHeight(new_rect.height() * (node_inputs_.size() + 1)); - } - - setRect(new_rect); - } else { - setRect(title_bar_rect_); + if (IsOutputItem()) { + // We don't have to check has_connectable_inputs_ here because we did it at the top + input_connector_->setVisible(!expanded_); } - UpdateInputConnectorFlowDirections(); + if (expanded_) { + if (IsOutputItem()) { + // Create items for each input of the node + int i = 1; + foreach (const QString &input, node_->inputs()) { + if (IsInputValid(input)) { + NodeViewItem *item = new NodeViewItem(node_, input, -1, context_, this); + item->setPos(QPointF(0, i * item->rect().height())); + children_.append(item); + i++; + } + } - UpdateConnectorPositions(); + QVector edges = edges_; + for (auto it=edges.cbegin(); it!=edges.cend(); it++) { + if ((*it)->to_item() == this) { + (*it)->set_to_item(GetItemForInput((*it)->input())); + } + } + + SetRectSize(i); + } else { + // Create items for each element of the input array + int arr_sz = node_->InputArraySize(input_); + children_.resize(arr_sz); + for (int i=0; isetPos(pos() + QPointF(0, (i+1) * item->rect().height())); + children_[i] = item; + } + + QVector edges = edges_; + for (auto it=edges.cbegin(); it!=edges.cend(); it++) { + if ((*it)->to_item() == this) { + (*it)->set_to_item(GetItemForInput((*it)->input())); + } + } + } + } else { + foreach (NodeViewItem *child, children_) { + QVector child_edges = child->edges(); + foreach (NodeViewEdge *edge, child_edges) { + edge->set_to_item(this); + } + delete child; + } + children_.clear(); + + SetRectSize(1); + } + + if (flow_dir_ == NodeViewCommon::kTopToBottom) { + UpdateOutputConnectorPosition(); + } ReadjustAllEdges(); @@ -277,35 +325,16 @@ void NodeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti // has been slightly modified QPalette app_pal = Core::instance()->main_window()->palette(); - // Draw background rect if expanded - if (IsExpanded()) { - painter->setPen(Qt::NoPen); - painter->setBrush(app_pal.color(QPalette::Window)); - - painter->drawRect(rect()); - - painter->setPen(app_pal.color(QPalette::Text)); - - for (int i=0;ifillRect(input_rect, highlight_col); - } - - painter->drawText(input_rect, Qt::AlignCenter, node_->GetInputName(node_inputs_.at(i))); - } - } - // Draw the titlebar - if (!hide_titlebar_ && node_) { + if (IsOutputItem()) { + QRectF single_unit_rect = rect(); + single_unit_rect.setHeight(DefaultItemHeight()); + // Output item drawing code painter->setPen(Qt::black); - painter->setBrush(node_->brush(title_bar_rect_.top(), title_bar_rect_.bottom())); + painter->setBrush(node_->brush(single_unit_rect.top(), single_unit_rect.bottom())); - painter->drawRect(title_bar_rect_); + painter->drawRect(single_unit_rect); painter->setPen(app_pal.color(QPalette::Text)); @@ -320,40 +349,54 @@ void NodeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti int icon_size = painter->fontMetrics().height()/2; - bool draw_arrow = !node_inputs_.isEmpty(); - if (node_label.isEmpty()) { // Draw shortname only - DrawNodeTitle(painter, node_shortname, title_bar_rect_, Qt::AlignVCenter, icon_size, draw_arrow); + DrawNodeTitle(painter, node_shortname, single_unit_rect, Qt::AlignVCenter, icon_size, has_connectable_inputs_); } else { int text_pad = DefaultTextPadding()/2; - QRectF safe_label_bounds = title_bar_rect_.adjusted(text_pad, text_pad, -text_pad, -text_pad); + QRectF safe_label_bounds = single_unit_rect.adjusted(text_pad, text_pad, -text_pad, -text_pad); QFont f; qreal font_sz = f.pointSizeF(); f.setPointSizeF(font_sz * 0.8); painter->setFont(f); - DrawNodeTitle(painter, node_label, safe_label_bounds, Qt::AlignTop, icon_size, draw_arrow); + DrawNodeTitle(painter, node_label, safe_label_bounds, Qt::AlignTop, icon_size, has_connectable_inputs_); f.setPointSizeF(font_sz * 0.6); painter->setFont(f); DrawNodeTitle(painter, node_shortname, safe_label_bounds, Qt::AlignBottom, icon_size, false); } - } + // Draw final border + QPen border_pen; + border_pen.setWidth(node_border_width_); - // Draw final border - QPen border_pen; - border_pen.setWidth(node_border_width_); + if (option->state & QStyle::State_Selected) { + border_pen.setColor(app_pal.color(QPalette::Highlight)); + } else { + border_pen.setColor(Qt::black); + } - if (option->state & QStyle::State_Selected) { - border_pen.setColor(app_pal.color(QPalette::Highlight)); + painter->setPen(border_pen); + painter->setBrush(Qt::NoBrush); + + painter->drawRect(rect()); } else { - border_pen.setColor(Qt::black); + // Input item drawing code + painter->setPen(Qt::NoPen); + painter->setBrush(app_pal.color(QPalette::Window)); + + painter->drawRect(rect()); + + if (highlighted_) { + QColor highlight_col = app_pal.color(QPalette::Text); + highlight_col.setAlpha(64); + painter->setBrush(highlight_col); + painter->drawRect(rect()); + } + + painter->setPen(app_pal.color(QPalette::Text)); + + painter->drawText(rect(), Qt::AlignCenter, node_->GetInputName(input_)); } - - painter->setPen(border_pen); - painter->setBrush(Qt::NoBrush); - - painter->drawRect(rect()); } void NodeViewItem::mousePressEvent(QGraphicsSceneMouseEvent *event) @@ -399,11 +442,16 @@ QVariant NodeViewItem::itemChange(QGraphicsItem::GraphicsItemChange change, cons void NodeViewItem::ReadjustAllEdges() { - UpdateInputConnectorFlowDirections(); - UpdateInputConnectorPositions(); foreach (NodeViewEdge* edge, edges_) { + if (NodeViewItem *to_item = edge->to_item()) { + static_cast(to_item->parentItem())->UpdateFlowDirectionOfInputItem(to_item); + } + edge->Adjust(); } + foreach (NodeViewItem *child, children_) { + child->ReadjustAllEdges(); + } } void NodeViewItem::UpdateContextRect() @@ -420,19 +468,19 @@ void NodeViewItem::DrawNodeTitle(QPainter* painter, QString text, const QRectF& painter->setRenderHint(QPainter::SmoothPixmapTransform); // Draw right or down arrow based on expanded state - int icon_padding = title_bar_rect_.height() / 2 - icon_size / 2; + int icon_padding = DefaultItemHeight() / 2 - icon_size / 2; int icon_full_size = icon_size + icon_padding * 2; if (draw_arrow) { const QIcon& expand_icon = IsExpanded() ? icon::TriDown : icon::TriRight; int icon_size_scaled = icon_size * painter->transform().m11(); - painter->drawPixmap(QRect(title_bar_rect_.x() + icon_padding, - title_bar_rect_.y() + icon_padding, + painter->drawPixmap(QRect(this->rect().x() + icon_padding, + this->rect().y() + icon_padding, icon_size, icon_size), expand_icon.pixmap(QSize(icon_size_scaled, icon_size_scaled))); } // Calculate how much space we have for text - int item_width = title_bar_rect_.width(); + int item_width = this->rect().width(); int max_text_width = item_width - DefaultTextPadding() * 2 - icon_full_size; int label_width = QtUtils::QFontMetricsWidth(fm, text); @@ -467,28 +515,6 @@ void NodeViewItem::DrawNodeTitle(QPainter* painter, QString text, const QRectF& text); } -NodeViewEdge *NodeViewItem::GetEdgeFromInputIndex(int index) -{ - foreach (NodeViewEdge *edge, edges_) { - if (edge->input().input() == node_inputs_.at(index)) { - return edge; - } - } - - return nullptr; -} - -void NodeViewItem::SetHighlightedIndex(int index) -{ - if (highlighted_index_ == index) { - return; - } - - highlighted_index_ = index; - - update(); -} - void NodeViewItem::SetLabelAsOutput(bool e) { label_as_output_ = e; @@ -496,58 +522,9 @@ void NodeViewItem::SetLabelAsOutput(bool e) update(); } -NodeInput NodeViewItem::GetInputFromInputConnector(NodeViewItemConnector *connector) +QPointF NodeViewItem::GetInputPoint() const { - for (int i=0; i= int(input_connectors_.size())) { - return pos(); - } - - return input_connectors_[index]->scenePos(); + return input_connector_->scenePos(); } QPointF NodeViewItem::GetOutputPoint() const @@ -576,13 +553,21 @@ QPointF NodeViewItem::GetOutputPoint() const void NodeViewItem::SetFlowDirection(NodeViewCommon::FlowDirection dir) { - flow_dir_ = dir; + if (flow_dir_ != dir) { + flow_dir_ = dir; - UpdateInputConnectorFlowDirections(); - output_connector_->SetFlowDirection(dir); + input_connector_->SetFlowDirection(dir); + output_connector_->SetFlowDirection(dir); - UpdateConnectorPositions(); - UpdateNodePosition(); + UpdateInputConnectorPosition(); + UpdateOutputConnectorPosition(); + + if (IsOutputItem()) { + UpdateNodePosition(); + } + + ReadjustAllEdges(); + } } void NodeViewItem::UpdateNodePosition() @@ -590,26 +575,47 @@ void NodeViewItem::UpdateNodePosition() setPos(NodeToScreenPoint(cached_node_pos_, flow_dir_)); } -void NodeViewItem::UpdateInputConnectors() +void NodeViewItem::UpdateInputConnectorPosition() { - int old_sz = input_connectors_.size(); + QRectF output_rect = input_connector_->boundingRect(); - input_connectors_.resize(node_inputs_.size()); - for (size_t i=old_sz; i(false, this); + NodeViewCommon::FlowDirection using_flow_dir = flow_dir_; + + if (IsExpanded() && !NodeViewCommon::IsFlowHorizontal(flow_dir_)) { + if (edges_.isEmpty() || edges_.first()->from_item()->x() < this->x()) { + using_flow_dir = NodeViewCommon::kLeftToRight; + } else { + using_flow_dir = NodeViewCommon::kRightToLeft; + } + } + + // Input connector flow directions change conditionally + switch (using_flow_dir) { + case NodeViewCommon::kLeftToRight: + input_connector_->setPos(rect().left() - output_rect.width(), 0); + break; + case NodeViewCommon::kRightToLeft: + input_connector_->setPos(rect().right() + output_rect.width(), 0); + break; + case NodeViewCommon::kTopToBottom: + input_connector_->setPos(rect().center().x(), rect().top() - output_rect.height()); + break; + case NodeViewCommon::kBottomToTop: + input_connector_->setPos(rect().center().x(), rect().bottom() + output_rect.height()); + break; + case NodeViewCommon::kInvalidDirection: + break; } } -void NodeViewItem::UpdateConnectorPositions() +void NodeViewItem::UpdateOutputConnectorPosition() { - UpdateInputConnectorPositions(); - switch (flow_dir_) { case NodeViewCommon::kLeftToRight: - output_connector_->setPos(rect().right(), title_bar_rect_.center().y()); + output_connector_->setPos(rect().right(), 0); break; case NodeViewCommon::kRightToLeft: - output_connector_->setPos(rect().left(), title_bar_rect_.center().y()); + output_connector_->setPos(rect().left(), 0); break; case NodeViewCommon::kTopToBottom: output_connector_->setPos(rect().center().x(), rect().bottom()); @@ -617,57 +623,94 @@ void NodeViewItem::UpdateConnectorPositions() case NodeViewCommon::kBottomToTop: output_connector_->setPos(rect().center().x(), rect().top()); break; + case NodeViewCommon::kInvalidDirection: + break; } } -void NodeViewItem::UpdateInputConnectorPositions() +bool NodeViewItem::IsInputValid(const QString &input) { - QRectF output_rect = output_connector_->boundingRect(); - - // Input connector flow directions change conditionally - for (size_t i=0; isetPos(rect().left() - output_rect.width(), GetInputRect(i).center().y()); - break; - case NodeViewCommon::kRightToLeft: - input_connectors_[i]->setPos(rect().right() + output_rect.width(), GetInputRect(i).center().y()); - break; - case NodeViewCommon::kTopToBottom: - input_connectors_[i]->setPos(rect().center().x(), rect().top() - output_rect.height()); - break; - case NodeViewCommon::kBottomToTop: - input_connectors_[i]->setPos(rect().center().x(), rect().bottom() + output_rect.height()); - break; - } - } + return node_->IsInputConnectable(input) && !node_->IsInputHidden(input); } -void NodeViewItem::UpdateInputConnectorFlowDirections() +void NodeViewItem::SetRectSize(int height_units) { - for (size_t i=0; iSetFlowDirection(GetFlowDirectionForInput(i)); - } + // Set rect + int widget_width = DefaultItemWidth(); + int widget_height = DefaultItemHeight(); + + setRect(QRectF(-widget_width/2, -widget_height/2, widget_width, widget_height * height_units)); } -NodeViewCommon::FlowDirection NodeViewItem::GetFlowDirectionForInput(int index) +void NodeViewItem::UpdateFlowDirectionOfInputItem(NodeViewItem *child) { - if (!expanded_ || NodeViewCommon::IsFlowHorizontal(flow_dir_)) { - return flow_dir_; - } else { - NodeViewEdge *edge = GetEdgeFromInputIndex(index); - - if (!edge || edge->from_item()->x() < this->x()) { - return NodeViewCommon::kLeftToRight; + if (!child->IsOutputItem()) { + if (NodeViewCommon::IsFlowVertical(flow_dir_)) { + if (!child->edges().isEmpty() && child->edges().first()->from_item()->scenePos().x() > child->scenePos().x()) { + child->SetFlowDirection(NodeViewCommon::kRightToLeft); + } else { + child->SetFlowDirection(NodeViewCommon::kLeftToRight); + } } else { - return NodeViewCommon::kRightToLeft; + child->SetFlowDirection(flow_dir_); } } } +void NodeViewItem::RepopulateInputs() +{ + has_connectable_inputs_ = false; + + foreach (const QString& input, node_->inputs()) { + if (IsInputValid(input)) { + has_connectable_inputs_ = true; + break; + } + } + + input_connector_->setVisible(has_connectable_inputs_); +} + void NodeViewItem::NodeAppearanceChanged() { update(); } +void NodeViewItem::SetHighlighted(bool e) +{ + highlighted_ = e; + update(); +} + +NodeViewItem *NodeViewItem::GetItemForInput(NodeInput input) +{ + if (NodeGroup *group = dynamic_cast(node_)) { + if (input.node() != group) { + // Translate input to group input + QString id = NodeGroup::GetGroupInputIDFromInput(input); + input.set_node(group); + input.set_input(id); + } + } + + if (IsExpanded()) { + if (input_.isEmpty()) { + // Look for the input in our children + foreach (NodeViewItem *i, children_) { + if (i->input_ == input.input()) { + return i; + } + } + } else { + // Look for element in our children + if (input.element() >= 0 && input.element() < children_.size()) { + return children_.at(input.element()); + } + } + } + + // Fallback to this object + return this; +} + } diff --git a/app/widget/nodeview/nodeviewitem.h b/app/widget/nodeview/nodeviewitem.h index 60d1f3874..4b2c7236f 100644 --- a/app/widget/nodeview/nodeviewitem.h +++ b/app/widget/nodeview/nodeviewitem.h @@ -32,6 +32,7 @@ namespace olive { +class NodeViewItem; class NodeViewEdge; /** @@ -45,11 +46,19 @@ class NodeViewItem : public QObject, public QGraphicsRectItem { Q_OBJECT public: - NodeViewItem(Node* n, Node *context, QGraphicsItem* parent = nullptr); + NodeViewItem(Node *node, const QString &input, int element, Node *context, QGraphicsItem* parent = nullptr); + NodeViewItem(Node *node, Node *context, QGraphicsItem* parent = nullptr) : + NodeViewItem(node, QString(), -1, context, parent) + { + } + + virtual ~NodeViewItem() override; QPointF GetNodePosition() const; void SetNodePosition(const QPointF& pos); + QVector GetAllEdgesRecursively() const; + /** * @brief Get currently attached node */ @@ -58,6 +67,11 @@ public: return node_; } + NodeInput GetInput() const + { + return NodeInput(node_, input_, element_); + } + Node *GetContext() const { return context_; @@ -82,11 +96,7 @@ public: void SetExpanded(bool e, bool hide_titlebar = false); void ToggleExpanded(); - /** - * @brief Returns GLOBAL point that edges should connect to for any NodeParam member of this object - */ - QPointF GetInputPoint(const QString& input, int element) const; - + QPointF GetInputPoint() const; QPointF GetOutputPoint() const; /** @@ -94,6 +104,11 @@ public: */ void SetFlowDirection(NodeViewCommon::FlowDirection dir); + NodeViewCommon::FlowDirection GetFlowDirection() const + { + return flow_dir_; + } + static int DefaultTextPadding(); static int DefaultItemHeight(); @@ -113,19 +128,20 @@ public: void AddEdge(NodeViewEdge* edge); void RemoveEdge(NodeViewEdge* edge); - int GetIndexAt(QPointF pt) const; - - NodeInput GetInputAtIndex(int index) const - { - return NodeInput(node_, node_inputs_.at(index)); - } - - void SetHighlightedIndex(int index); - void SetLabelAsOutput(bool e); - NodeInput GetInputFromInputConnector(NodeViewItemConnector *connector); - NodeViewEdge *GetEdgeFromInputConnector(NodeViewItemConnector *connector); + void SetHighlighted(bool e); + + NodeViewItem *GetItemForInput(NodeInput input); + + bool IsOutputItem() const + { + return input_.isEmpty(); + } + + void ReadjustAllEdges(); + + void UpdateFlowDirectionOfInputItem(NodeViewItem *child); protected: virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; @@ -138,49 +154,35 @@ protected: virtual QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override; private: - void ReadjustAllEdges(); - void UpdateContextRect(); void DrawNodeTitle(QPainter *painter, QString text, const QRectF &rect, Qt::Alignment vertical_align, int icon_size, bool draw_arrow); - NodeViewEdge *GetEdgeFromInputIndex(int index); - - /** - * @brief Returns local rect of a NodeInput in array node_inputs_[index] - */ - QRectF GetInputRect(int index) const; - /** * @brief Internal update function when logical position changes */ void UpdateNodePosition(); - void UpdateInputConnectors(); + void UpdateInputConnectorPosition(); + void UpdateOutputConnectorPosition(); - void UpdateConnectorPositions(); + bool IsInputValid(const QString &input); - void UpdateInputConnectorPositions(); - void UpdateInputConnectorFlowDirections(); - - NodeViewCommon::FlowDirection GetFlowDirectionForInput(int index); + void SetRectSize(int height_units = 1); /** * @brief Reference to attached Node */ - Node* node_; + Node *node_; + QString input_; + int element_; Node *context_; /** * @brief Cached list of node inputs */ - QVector node_inputs_; - - /** - * @brief Rectangle of the Node's title bar (equal to rect() when collapsed) - */ - QRectF title_bar_rect_; + QVector children_; /// Sizing variables to use when drawing int node_border_width_; @@ -190,9 +192,7 @@ private: */ bool expanded_; - bool hide_titlebar_; - - int highlighted_index_; + bool highlighted_; NodeViewCommon::FlowDirection flow_dir_; @@ -200,14 +200,18 @@ private: QPointF cached_node_pos_; - std::vector > input_connectors_; + NodeViewItemConnector *input_connector_; NodeViewItemConnector *output_connector_; + bool has_connectable_inputs_; + bool label_as_output_; private slots: void NodeAppearanceChanged(); + void RepopulateInputs(); + }; } diff --git a/app/widget/nodeview/nodeviewitemconnector.cpp b/app/widget/nodeview/nodeviewitemconnector.cpp index 2ab919d77..032b9903f 100644 --- a/app/widget/nodeview/nodeviewitemconnector.cpp +++ b/app/widget/nodeview/nodeviewitemconnector.cpp @@ -74,6 +74,8 @@ void NodeViewItemConnector::SetFlowDirection(NodeViewCommon::FlowDirection dir) p[1] = QPointF(-triangle_sz_half, 0); p[2] = QPointF(0, triangle_sz_half); break; + case NodeViewCommon::kInvalidDirection: + break; } setPolygon(p); diff --git a/app/widget/timelinewidget/undo/timelineundogeneral.cpp b/app/widget/timelinewidget/undo/timelineundogeneral.cpp index 9c8bbae14..a64d1e342 100644 --- a/app/widget/timelinewidget/undo/timelineundogeneral.cpp +++ b/app/widget/timelinewidget/undo/timelineundogeneral.cpp @@ -153,7 +153,7 @@ void TimelineAddTrackCommand::redo() if (create_pos_command) { position_command_->add_child(new NodeSetPositionCommand(track_, sequence, sequence->GetNodePositionInContext(sequence) + QPointF(-1, -position_factor))); position_command_->add_child(new NodeSetPositionCommand(merge_, sequence, sequence->GetNodePositionInContext(sequence))); - position_command_->add_child(new NodeSetPositionCommand(merge_, sequence, sequence->GetNodePositionInContext(sequence) + QPointF(-1, position_factor * timeline_->GetTrackCount()), true)); + position_command_->add_child(new NodeSetPositionAndDependenciesRecursivelyCommand(merge_, sequence, sequence->GetNodePositionInContext(sequence) + QPointF(-1, position_factor * timeline_->GetTrackCount()))); } } else if (direct_.IsValid() && !direct_.IsConnected()) { // If no merge, we have a direct connection, and nothing else is connected, connect this diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index db245f005..ddc0e6c0c 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -750,7 +750,7 @@ void MainWindow::SetDefaultLayout() node_panel_->show(); tabifyDockWidget(param_panel_, node_panel_); - footage_viewer_panel_->raise(); + param_panel_->raise(); curve_panel_->hide(); curve_panel_->setFloating(true);