diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 22e8520e4..01d55d6da 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -27,6 +27,8 @@ #include "core.h" #include "nodeviewundo.h" +#include "node/audio/volume/volume.h" +#include "node/distort/transform/transformdistortnode.h" #include "node/factory.h" #include "node/traverser.h" #include "widget/menu/menushared.h" @@ -71,6 +73,10 @@ NodeView::NodeView(QWidget *parent) : connect(minimap_, &NodeViewMiniMap::MoveToScenePoint, this, &NodeView::MoveToScenePoint); connect(horizontalScrollBar(), &QScrollBar::valueChanged, this, &NodeView::UpdateViewportOnMiniMap); connect(verticalScrollBar(), &QScrollBar::valueChanged, this, &NodeView::UpdateViewportOnMiniMap); + + reposition_contexts_timer_.setInterval(1); + reposition_contexts_timer_.setSingleShot(true); + connect(&reposition_contexts_timer_, &QTimer::timeout, this, &NodeView::RepositionContexts); } NodeView::~NodeView() @@ -95,6 +101,7 @@ void NodeView::SetGraph(NodeGraph *graph, const QVector &nodes) DeselectAll(); positions_.clear(); scene_.clear(); + context_offsets_.clear(); } // Handle graph change @@ -127,47 +134,12 @@ void NodeView::SetGraph(NodeGraph *graph, const QVector &nodes) } if (refresh_required && nodes_visible) { - if (filter_mode_ == kFilterShowAll) { - // Determine which contexts in the graph have no outputs, these are considered "root-level" - filter_nodes_.clear(); - for (auto it=graph->GetPositionMap().cbegin(); it!=graph->GetPositionMap().cend(); it++) { - Node *context = it.key(); - if (context->output_connections().empty()) { - filter_nodes_.append(context); - } - } + // Just make the filter nodes all of the graph's contexts + filter_nodes_ = graph->GetPositionMap().keys().toVector(); } - // Standard root-level positioning code - qreal last_offset = 0; - int additional_spacing = 0; - - // Contexts may be added to this later, so to ensure we only process the root-level nodes we - // found above, we'll store the current size and stop iterating after that - int sz = filter_nodes_.size(); - for (int i=0; iGetNodesForContext(n); - - // First determine the total "height" of this graph and how much we need to offset it - qreal top = 0; - qreal bottom = 0; - for (auto it=map.cbegin(); it!=map.cend(); it++) { - const QPointF &node_pos_in_context = it.value(); - top = qMin(node_pos_in_context.y(), top); - bottom = qMax(node_pos_in_context.y(), bottom); - } - - last_offset += (additional_spacing + (bottom - top)); - additional_spacing = 1; - context_offsets_.insert(n, QPointF(0, last_offset)); - - // Finally add all nodes - for (auto it=map.cbegin(); it!=map.cend(); it++) { - AddNodePosition(it.key(), n); - } - } + RepositionContexts(); // Center on something QMetaObject::invokeMethod(this, &NodeView::CenterOnItemsBoundingRect, Qt::QueuedConnection); @@ -219,7 +191,7 @@ void NodeView::DeleteSelected() } if (!selected_nodes.isEmpty()) { - foreach (Node* node, selected_nodes) { + for (Node* node : selected_nodes) { command->add_child(new NodeRemoveAndDisconnectCommand(node)); } } @@ -248,7 +220,7 @@ void NodeView::SelectAll() } else { // We have to determine the difference QVector new_selection; - foreach (Node* n, graph_->nodes()) { + for (Node* n : graph_->nodes()) { if (!selected_nodes_.contains(n)) { new_selection.append(n); } @@ -300,7 +272,7 @@ void NodeView::Select(QVector nodes, bool center_view_on_item) NodeViewItem *first_item = nullptr; - foreach (Node* n, nodes) { + for (Node* n : nodes) { if (processed.contains(n)) { continue; } @@ -420,7 +392,7 @@ void NodeView::Duplicate() void NodeView::SetColorLabel(int index) { - foreach (Node* node, selected_nodes_) { + for (Node* node : selected_nodes_) { node->SetOverrideColor(index); } } @@ -445,8 +417,8 @@ void NodeView::keyPressEvent(QKeyEvent *event) { if (graph_) { MultiUndoCommand *pos_command = new MultiUndoCommand(); - foreach (Node *n, selected_nodes_) { - foreach (Node *context, filter_nodes_) { + for (Node *n : selected_nodes_) { + for (Node *context : filter_nodes_) { if (graph_->GetNodesForContext(context).contains(n)) { QPointF old_pos = graph_->GetNodePosition(n, context); @@ -511,7 +483,7 @@ void NodeView::mousePressEvent(QMouseEvent *event) // See if we're dragging the arrow of an edge QPointF scene_pt = mapToScene(event->pos()); - foreach (NodeViewEdge *edge_item, scene_.edges()) { + for (NodeViewEdge *edge_item : scene_.edges()) { if (edge_item->arrow_bounding_rect().contains(scene_pt)) { create_edge_src_ = scene_.NodeToUIObject(edge_item->output().node()); create_edge_src_output_ = edge_item->output().output(); @@ -522,7 +494,7 @@ void NodeView::mousePressEvent(QMouseEvent *event) } // See if we're dragging the arrow of a node - foreach (NodeViewItem *node_item, scene_.item_map()) { + for (NodeViewItem *node_item : scene_.item_map()) { if (node_item->GetOutputTriangle().boundingRect().translated(node_item->pos()).contains(scene_pt)) { CreateNewEdge(node_item); return; @@ -648,7 +620,7 @@ void NodeView::mouseMoveEvent(QMouseEvent *event) NodeViewEdge* new_drop_edge = nullptr; // See if there is an edge here - foreach (QGraphicsItem* item, items) { + for (QGraphicsItem* item : items) { new_drop_edge = dynamic_cast(item); if (new_drop_edge) { @@ -665,7 +637,7 @@ void NodeView::mouseMoveEvent(QMouseEvent *event) // Iterate through the inputs of our dragging node and see if our node has any acceptable // inputs to connect to for this type - foreach (const QString& input, attached_node->inputs()) { + for (const QString& input : attached_node->inputs()) { NodeInput i(attached_node, input); if (attached_node->IsInputConnectable(input)) { @@ -802,7 +774,7 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) if (pos_data.original_item_pos != current_item_pos) { QPointF diff = current_item_pos - pos_data.original_item_pos; - foreach (Node *context, filter_nodes_) { + for (Node *context : filter_nodes_) { if (graph_->ContextContainsNode(node, context)) { QPointF current_node_pos_in_context = graph_->GetNodePosition(node, context); current_node_pos_in_context += diff; @@ -852,13 +824,13 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) // Remove from context any nodes that don't specifically output to said context MultiUndoCommand *remove_pos_command = new MultiUndoCommand(); - foreach (const AttachedItem &attached, attached_items_) { + for (const AttachedItem &attached : attached_items_) { MultiUndoCommand *remove_pos_subcommand = new MultiUndoCommand(); Node *attached_node = scene_.item_map().key(attached.item); bool removed = false; QVector relevant_contexts; - foreach (Node *context, filter_nodes_) { + for (Node *context : filter_nodes_) { if (attached_node->OutputsTo(context, true)) { relevant_contexts.append(context); } else { @@ -868,7 +840,7 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) } if (removed && !relevant_contexts.isEmpty()) { - foreach (Node *relevant, relevant_contexts) { + for (Node *relevant : relevant_contexts) { remove_pos_subcommand->add_child(new NodeSetPositionCommand(attached_node, relevant, GetEstimatedPositionForContext(attached.item, relevant), false)); } @@ -913,7 +885,7 @@ void NodeView::UpdateSelectionCache() // All nodes in the current selection have just been selected selected = current_selection; } else { - foreach (Node* n, current_selection) { + for (Node* n : current_selection) { if (!selected_nodes_.contains(n)) { selected.append(n); } @@ -925,7 +897,7 @@ void NodeView::UpdateSelectionCache() // All nodes that were selected have been deselected deselected = selected_nodes_; } else { - foreach (Node* n, selected_nodes_) { + for (Node* n : selected_nodes_) { if (!current_selection.contains(n)) { deselected.append(n); } @@ -1043,7 +1015,7 @@ void NodeView::CreateNodeSlot(QAction *action) if (new_node) { paste_command_ = new MultiUndoCommand(); paste_command_->add_child(new NodeAddCommand(graph_, new_node)); - foreach (Node *context, filter_nodes_) { + for (Node *context : filter_nodes_) { paste_command_->add_child(new NodeSetPositionCommand(new_node, context, QPointF(0, 0), false)); } paste_command_->add_child(new NodeViewAttachNodesToCursor(this, {new_node})); @@ -1062,7 +1034,7 @@ void NodeView::AutoPositionDescendents() { QVector selected = scene_.GetSelectedNodes(); - foreach (Node* n, selected) { + for (Node* n : selected) { scene_.ReorganizeFrom(n); } } @@ -1129,52 +1101,25 @@ void NodeView::RemoveEdge(const NodeOutput &output, const NodeInput &input) void NodeView::AddNodePosition(Node *node, Node *relative) { - if (filter_nodes_.contains(relative)) { - // Get UI item or create if it doesn't exist - NodeViewItem *item = scene_.item_map().value(node); - if (!item) { - item = scene_.AddNode(node); + bool listening_to_node = filter_nodes_.contains(relative); - for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { - if (scene_.item_map().contains(it->second.node())) { - scene_.AddEdge(it->second, it->first); - } - } - - for (auto it=node->output_connections().cbegin(); it!=node->output_connections().cend(); it++) { - if (scene_.item_map().contains(it->second.node())) { - scene_.AddEdge(it->first, it->second); - } - } + if (!listening_to_node) { + if (filter_mode_ == kFilterShowAll) { + // We're not listening to this context, but because we're showing all, add it + filter_nodes_.append(relative); + } else { + // Ignore signal + return; } + } - // Determine "view" position by averaging the Y value and "min"ing the X value of all contexts - QPointF item_pos(DBL_MAX, 0.0); - int average_count = 0; - foreach (Node *context, filter_nodes_) { - if (graph_->GetNodesForContext(context).contains(node)) { - QPointF this_context_pos = graph_->GetNodePosition(node, context); - this_context_pos += context_offsets_.value(context); - item_pos.setX(qMin(item_pos.x(), this_context_pos.x())); - item_pos.setY(item_pos.y() + this_context_pos.y()); - average_count++; - } - } - item_pos.setY(item_pos.y() / average_count); + // Reposition contexts because one of their heights may have changed or a new one may have been + // added + UpdateNodeItem(node); - // Set position - item->SetNodePosition(item_pos); - positions_.insert(item, {node, item_pos}); - - // For "show all" mode, we recursively add more nodes to the graph - if (filter_mode_ == kFilterShowAll && !filter_nodes_.contains(node) && graph_->GetPositionMap().contains(node)) { - filter_nodes_.append(node); - context_offsets_.insert(node, item_pos); - const NodeGraph::PositionMap &map = graph_->GetNodesForContext(node); - for (auto it=map.cbegin(); it!=map.cend(); it++) { - AddNodePosition(it.key(), node); - } - } + if (filter_mode_ == kFilterShowAll) { + reposition_contexts_timer_.stop(); + reposition_contexts_timer_.start(); } } @@ -1187,7 +1132,7 @@ void NodeView::RemoveNodePosition(Node *node, Node *relative) // Determine if any other contexts have this node bool found = false; - foreach (Node *context, filter_nodes_) { + for (Node *context : filter_nodes_) { if (graph_->ContextContainsNode(node, context)) { found = true; break; @@ -1195,7 +1140,7 @@ void NodeView::RemoveNodePosition(Node *node, Node *relative) } if (!found) { - foreach (const Node::OutputConnection &oc, node->output_connections()) { + for (const Node::OutputConnection &oc : node->output_connections()) { scene_.RemoveEdge(oc.first, oc.second); } for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { @@ -1205,6 +1150,10 @@ void NodeView::RemoveNodePosition(Node *node, Node *relative) scene_.RemoveNode(node); } } + + if (filter_mode_ == kFilterShowAll) { + RepositionContexts(); + } } } @@ -1275,7 +1224,7 @@ void NodeView::AttachItemsToCursor(const QVector& items) DetachItemsFromCursor(); if (!items.isEmpty()) { - foreach (NodeViewItem* i, items) { + for (NodeViewItem* i : items) { attached_items_.append({i, i->pos() - items.first()->pos()}); } @@ -1297,7 +1246,7 @@ void NodeView::MoveAttachedNodesToCursor(const QPoint& p) { QPointF item_pos = mapToScene(p); - foreach (const AttachedItem& i, attached_items_) { + for (const AttachedItem& i : attached_items_) { i.item->setPos(item_pos + i.original_pos); } } @@ -1362,7 +1311,7 @@ void NodeView::ZoomFromKeyboard(double multiplier) bool NodeView::DetermineIfNodeIsFloatingInContext(Node *node, Node *context, Node *source, const Node::OutputConnections &removed_edges, const Node::OutputConnection &added_edge) { // Determines whether `node` outputs to another node in `context` besides `source` - foreach (const Node::OutputConnection &conn, node->output_connections()) { + for (const Node::OutputConnection &conn : node->output_connections()) { Node *output_candidate = conn.second.node(); if (output_candidate == source) { @@ -1382,7 +1331,7 @@ bool NodeView::DetermineIfNodeIsFloatingInContext(Node *node, Node *context, Nod void NodeView::UpdateContextsFromEdgeRemove(MultiUndoCommand *command, const Node::OutputConnections &remove_edges) { // For each edge we remove, determine if we should remove the node from a context as well - foreach (const Node::OutputConnection &edge, remove_edges) { + for (const Node::OutputConnection &edge : remove_edges) { Node *output_node = edge.first.node(); QVector contexts_to_remove_from; int contexts_containing = 0; @@ -1408,7 +1357,7 @@ void NodeView::UpdateContextsFromEdgeRemove(MultiUndoCommand *command, const Nod // Not removing from all contexts, can remove bool removing_from_all_current_contexts = true; - foreach (Node *context, filter_nodes_) { + for (Node *context : filter_nodes_) { if (graph_->ContextContainsNode(output_node, context)) { if (!contexts_to_remove_from.contains(context)) { removing_from_all_current_contexts = false; @@ -1417,7 +1366,7 @@ void NodeView::UpdateContextsFromEdgeRemove(MultiUndoCommand *command, const Nod } } - foreach (Node *context, contexts_to_remove_from) { + for (Node *context : contexts_to_remove_from) { RecursivelyRemoveFloatingNodeFromContext(command, output_node, context, output_node, remove_edges, Node::OutputConnection(), removing_from_all_current_contexts); } } @@ -1485,13 +1434,13 @@ void NodeView::UpdateContextsFromEdgeAdd(MultiUndoCommand *command, const Node:: if (node_is_floating) { // This action will unfloat this node, so remove it from all current contexts - foreach (Node *context, current_contexts) { + for (Node *context : current_contexts) { RecursivelyRemoveFloatingNodeFromContext(command, connecting_node, context, connecting_node, removed_edges, added_edge, false); } } // Add nodes to contexts - foreach (Node *context, contexts_to_add_to) { + for (Node *context : contexts_to_add_to) { RecursivelyAddNodeToContext(command, connecting_node, context); } } @@ -1523,6 +1472,176 @@ void NodeView::CreateNewEdge(NodeViewItem *output_item) scene_.addItem(create_edge_); } +void NodeView::RepositionContexts() +{ + // Determine which contexts are root-level + QVector root_level_nodes; + QVector non_root_level_nodes; + + for (Node *context : filter_nodes_) { + bool is_root_level = true; + + for (Node *context2 : filter_nodes_) { + if (context != context2 && graph_->ContextContainsNode(context, context2)) { + is_root_level = false; + break; + } + } + + if (is_root_level) { + root_level_nodes.append(context); + } else { + non_root_level_nodes.append(context); + } + } + + { + // Position root-level nodes + qreal last_offset = 0; + int additional_spacing = 0; + + for (Node *n : root_level_nodes) { + const NodeGraph::PositionMap &map = graph_->GetNodesForContext(n); + + // First determine the total "height" of this graph and how much we need to offset it + qreal top = 0; + qreal bottom = 0; + for (auto it=map.cbegin(); it!=map.cend(); it++) { + const QPointF &node_pos_in_context = it.value(); + top = qMin(node_pos_in_context.y(), top); + bottom = qMax(node_pos_in_context.y(), bottom); + } + + last_offset += (additional_spacing + (bottom - top)); + additional_spacing = 1; + context_offsets_.insert(n, QPointF(0, last_offset)); + } + } + + { + // Position non-root-level nodes + const bool optimized = true; + + qint64 t = QDateTime::currentMSecsSinceEpoch(); + + if (optimized) { + + while (!non_root_level_nodes.isEmpty()) { + QVector next_level_nodes; + + for (int i=0; iContextContainsNode(context, context2)) { + next_level = false; + break; + } + } + + if (next_level) { + next_level_nodes.append(context); + non_root_level_nodes.removeAt(i); + i--; + } + } + + for (Node *n : next_level_nodes) { + NodeViewItem *item = UpdateNodeItem(n); + + QPointF pos_in_context = graph_->GetNodesForContext(n).value(n); + + QPointF context_pos = item->GetNodePosition() - pos_in_context; + + context_offsets_.insert(n, context_pos); + } + } + + } else { + int iter = 0; + + while (true) { + bool changed = false; + + for (Node *n : non_root_level_nodes) { + NodeViewItem *item = UpdateNodeItem(n); + + QPointF pos_in_context = graph_->GetNodesForContext(n).value(n); + + QPointF context_pos = item->GetNodePosition() - pos_in_context; + + if (!context_offsets_.contains(n) || context_offsets_.value(n) != context_pos) { + context_offsets_.insert(n, context_pos); + changed = true; + } + } + + iter++; + + if (!changed) { + break; + } + } + } + + qDebug() << "Workflow took:" << (QDateTime::currentMSecsSinceEpoch() - t); + } + + { + // Position all other nodes + for (Node *context : filter_nodes_) { + const NodeGraph::PositionMap &map = graph_->GetNodesForContext(context); + for (auto it=map.cbegin(); it!=map.cend(); it++) { + UpdateNodeItem(it.key()); + } + } + } +} + +NodeViewItem *NodeView::UpdateNodeItem(Node *node) +{ + // Get UI item or create if it doesn't exist + NodeViewItem *item = scene_.item_map().value(node); + if (!item) { + item = scene_.AddNode(node); + + for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { + if (scene_.item_map().contains(it->second.node())) { + scene_.AddEdge(it->second, it->first); + } + } + + for (auto it=node->output_connections().cbegin(); it!=node->output_connections().cend(); it++) { + if (scene_.item_map().contains(it->second.node())) { + scene_.AddEdge(it->first, it->second); + } + } + } + + // Determine "view" position by averaging the Y value and "min"ing the X value of all contexts + QPointF item_pos(DBL_MAX, 0.0); + int average_count = 0; + for (Node *context : filter_nodes_) { + if (graph_->GetNodesForContext(context).contains(node)) { + QPointF this_context_pos = graph_->GetNodePosition(node, context); + this_context_pos += context_offsets_.value(context); + + item_pos.setX(qMin(item_pos.x(), this_context_pos.x())); + item_pos.setY(item_pos.y() + this_context_pos.y()); + average_count++; + } + } + item_pos.setY(item_pos.y() / average_count); + + // Set position + item->SetNodePosition(item_pos); + positions_.insert(item, {node, item_pos}); + + return item; +} + NodeView::NodeViewAttachNodesToCursor::NodeViewAttachNodesToCursor(NodeView *view, const QVector &nodes) : view_(view), nodes_(nodes) diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index dafb58812..99cbbaa1f 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -138,6 +138,8 @@ private: void CreateNewEdge(NodeViewItem *output_item); + NodeViewItem *UpdateNodeItem(Node *node); + class NodeViewAttachNodesToCursor : public UndoCommand { public: @@ -232,6 +234,8 @@ private: bool create_edge_already_exists_; + QTimer reposition_contexts_timer_; + static const double kMinimumScale; private slots: @@ -288,6 +292,8 @@ private slots: void MoveToScenePoint(const QPointF &pos); + void RepositionContexts(); + }; }