From 9f9b007cbf14eec1b7a187fb2e666c9f80bf22d9 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Mon, 19 Jul 2021 18:40:56 -0700 Subject: [PATCH] nodeview: significantly improve nodeview "show all" algorithm --- app/widget/nodeview/nodeview.cpp | 249 ++++++++++++++----------------- app/widget/nodeview/nodeview.h | 7 +- 2 files changed, 115 insertions(+), 141 deletions(-) diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 6f5972eb5..6f277e5a9 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -49,7 +49,8 @@ NodeView::NodeView(QWidget *parent) : create_edge_dst_temp_expanded_(false), paste_command_(nullptr), filter_mode_(kFilterShowSelective), - scale_(1.0) + scale_(1.0), + queue_reposition_contexts_(false) { setScene(&scene_); SetDefaultDragMode(RubberBandDrag); @@ -74,9 +75,7 @@ NodeView::NodeView(QWidget *parent) : 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); + viewport()->installEventFilter(this); } NodeView::~NodeView() @@ -88,7 +87,7 @@ NodeView::~NodeView() void NodeView::SetGraph(NodeGraph *graph, const QVector &nodes) { bool graph_changed = graph_ != graph; - bool context_changed = filter_nodes_ != nodes; + bool context_changed = last_set_filter_nodes_ != nodes; if (graph_changed || context_changed) { // Clear nodes if necessary @@ -127,8 +126,12 @@ void NodeView::SetGraph(NodeGraph *graph, const QVector &nodes) } } - if (context_changed && filter_mode_ == kFilterShowSelective) { - filter_nodes_ = nodes; + if (context_changed) { + last_set_filter_nodes_ = nodes; + + if (filter_mode_ == kFilterShowSelective) { + filter_nodes_ = nodes; + } } if (refresh_required && nodes_visible) { @@ -189,7 +192,7 @@ void NodeView::DeleteSelected() } if (!selected_nodes.isEmpty()) { - for (Node* node : selected_nodes) { + for (Node* node : qAsConst(selected_nodes)) { command->add_child(new NodeRemoveAndDisconnectCommand(node)); } } @@ -270,7 +273,7 @@ void NodeView::Select(QVector nodes, bool center_view_on_item) NodeViewItem *first_item = nullptr; - for (Node* n : nodes) { + for (Node* n : qAsConst(nodes)) { if (processed.contains(n)) { continue; } @@ -390,7 +393,7 @@ void NodeView::Duplicate() void NodeView::SetColorLabel(int index) { - for (Node* node : selected_nodes_) { + for (Node* node : qAsConst(selected_nodes_)) { node->SetOverrideColor(index); } } @@ -415,8 +418,8 @@ void NodeView::keyPressEvent(QKeyEvent *event) { if (graph_) { MultiUndoCommand *pos_command = new MultiUndoCommand(); - for (Node *n : selected_nodes_) { - for (Node *context : filter_nodes_) { + for (Node *n : qAsConst(selected_nodes_)) { + for (Node *context : qAsConst(filter_nodes_)) { if (graph_->GetNodesForContext(context).contains(n)) { QPointF old_pos = graph_->GetNodePosition(n, context); @@ -618,7 +621,7 @@ void NodeView::mouseMoveEvent(QMouseEvent *event) NodeViewEdge* new_drop_edge = nullptr; // See if there is an edge here - for (QGraphicsItem* item : items) { + for (QGraphicsItem* item : qAsConst(items)) { new_drop_edge = dynamic_cast(item); if (new_drop_edge) { @@ -772,7 +775,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; - for (Node *context : filter_nodes_) { + for (Node *context : qAsConst(filter_nodes_)) { if (graph_->ContextContainsNode(node, context)) { QPointF current_node_pos_in_context = graph_->GetNodePosition(node, context); current_node_pos_in_context += diff; @@ -822,13 +825,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(); - for (const AttachedItem &attached : attached_items_) { + for (const AttachedItem &attached : qAsConst(attached_items_)) { MultiUndoCommand *remove_pos_subcommand = new MultiUndoCommand(); Node *attached_node = scene_.item_map().key(attached.item); bool removed = false; QVector relevant_contexts; - for (Node *context : filter_nodes_) { + for (Node *context : qAsConst(filter_nodes_)) { if (attached_node->OutputsTo(context, true)) { relevant_contexts.append(context); } else { @@ -838,7 +841,7 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) } if (removed && !relevant_contexts.isEmpty()) { - for (Node *relevant : relevant_contexts) { + for (Node *relevant : qAsConst(relevant_contexts)) { remove_pos_subcommand->add_child(new NodeSetPositionCommand(attached_node, relevant, GetEstimatedPositionForContext(attached.item, relevant), false)); } @@ -883,7 +886,7 @@ void NodeView::UpdateSelectionCache() // All nodes in the current selection have just been selected selected = current_selection; } else { - for (Node* n : current_selection) { + for (Node* n : qAsConst(current_selection)) { if (!selected_nodes_.contains(n)) { selected.append(n); } @@ -895,7 +898,7 @@ void NodeView::UpdateSelectionCache() // All nodes that were selected have been deselected deselected = selected_nodes_; } else { - for (Node* n : selected_nodes_) { + for (Node* n : qAsConst(selected_nodes_)) { if (!current_selection.contains(n)) { deselected.append(n); } @@ -1007,7 +1010,7 @@ void NodeView::CreateNodeSlot(QAction *action) if (new_node) { paste_command_ = new MultiUndoCommand(); paste_command_->add_child(new NodeAddCommand(graph_, new_node)); - for (Node *context : filter_nodes_) { + for (Node *context : qAsConst(filter_nodes_)) { paste_command_->add_child(new NodeSetPositionCommand(new_node, context, QPointF(0, 0), false)); } paste_command_->add_child(new NodeViewAttachNodesToCursor(this, {new_node})); @@ -1029,7 +1032,7 @@ void NodeView::ContextMenuFilterChanged(QAction *action) if (filter_mode_ != mode) { // Store temporary graph variables NodeGraph *graph = graph_; - QVector nodes = filter_nodes_; + QVector nodes = last_set_filter_nodes_; // Unset graph with current filter mode ClearGraph(); @@ -1101,8 +1104,8 @@ void NodeView::AddNodePosition(Node *node, Node *relative) UpdateNodeItem(node); if (filter_mode_ == kFilterShowAll) { - reposition_contexts_timer_.stop(); - reposition_contexts_timer_.start(); + queue_reposition_contexts_ = true; + viewport()->update(); } } @@ -1115,7 +1118,7 @@ void NodeView::RemoveNodePosition(Node *node, Node *relative) // Determine if any other contexts have this node bool found = false; - for (Node *context : filter_nodes_) { + for (Node *context : qAsConst(filter_nodes_)) { if (graph_->ContextContainsNode(node, context)) { found = true; break; @@ -1229,7 +1232,7 @@ void NodeView::MoveAttachedNodesToCursor(const QPoint& p) { QPointF item_pos = mapToScene(p); - for (const AttachedItem& i : attached_items_) { + for (const AttachedItem& i : qAsConst(attached_items_)) { i.item->setPos(item_pos + i.original_pos); } } @@ -1279,6 +1282,18 @@ bool NodeView::event(QEvent *event) return super::event(event); } +bool NodeView::eventFilter(QObject *object, QEvent *event) +{ + if (object == viewport() && event->type() == QEvent::Paint) { + if (queue_reposition_contexts_) { + RepositionContexts(); + queue_reposition_contexts_ = false; + } + } + + return super::eventFilter(object, event); +} + void NodeView::ZoomFromKeyboard(double multiplier) { QPoint cursor_pos = mapFromGlobal(QCursor::pos()); @@ -1340,7 +1355,7 @@ void NodeView::UpdateContextsFromEdgeRemove(MultiUndoCommand *command, const Nod // Not removing from all contexts, can remove bool removing_from_all_current_contexts = true; - for (Node *context : filter_nodes_) { + for (Node *context : qAsConst(filter_nodes_)) { if (graph_->ContextContainsNode(output_node, context)) { if (!contexts_to_remove_from.contains(context)) { removing_from_all_current_contexts = false; @@ -1349,7 +1364,7 @@ void NodeView::UpdateContextsFromEdgeRemove(MultiUndoCommand *command, const Nod } } - for (Node *context : contexts_to_remove_from) { + for (Node *context : qAsConst(contexts_to_remove_from)) { RecursivelyRemoveFloatingNodeFromContext(command, output_node, context, output_node, remove_edges, Node::OutputConnection(), removing_from_all_current_contexts); } } @@ -1417,13 +1432,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 - for (Node *context : current_contexts) { + for (Node *context : qAsConst(current_contexts)) { RecursivelyRemoveFloatingNodeFromContext(command, connecting_node, context, connecting_node, removed_edges, added_edge, false); } } // Add nodes to contexts - for (Node *context : contexts_to_add_to) { + for (Node *context : qAsConst(contexts_to_add_to)) { RecursivelyAddNodeToContext(command, connecting_node, context); } } @@ -1458,132 +1473,84 @@ void NodeView::CreateNewEdge(NodeViewItem *output_item) void NodeView::RepositionContexts() { // Determine which contexts are root-level - QVector root_level_nodes; - QVector non_root_level_nodes; + QVector processing_filters = filter_nodes_; - for (Node *context : filter_nodes_) { - bool is_root_level = true; + // Level counter as we iterate through the list a few times + int level = 0; - for (Node *context2 : filter_nodes_) { - if (context != context2 && graph_->ContextContainsNode(context, context2)) { - is_root_level = false; - break; - } - } + // Root-level positioning variables + qreal last_offset = 0; + int additional_spacing = 0; - if (is_root_level) { - root_level_nodes.append(context); - } else { - non_root_level_nodes.append(context); - } - } + while (!processing_filters.isEmpty()) { + QVector contexts_on_this_level; - { - // Position root-level nodes - qreal last_offset = 0; - int additional_spacing = 0; + for (int i=0; iGetNodesForContext(n); + // Determine if this context is on an upper level or not + bool this_level = true; + for (int j=0; j 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) { + if (i != j && graph_->ContextContainsNode(context, other_context)) { + this_level = false; break; } } + + if (this_level) { + contexts_on_this_level.append(context); + } } - qDebug() << "Workflow took:" << (QDateTime::currentMSecsSinceEpoch() - t); + for (Node *context : qAsConst(contexts_on_this_level)) { + if (level == 0) { + const NodeGraph::PositionMap &map = graph_->GetNodesForContext(context); + + // 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(context, QPointF(0, last_offset)); + } else { + // Create/update item + NodeViewItem *item = UpdateNodeItem(context, true); + + // Get position generated by UpdateNodeItem + QPointF context_pos = item->GetNodePosition(); + + // Adjust by the context node's position in its own context (this will usually be 0,0) + context_pos -= graph_->GetNodesForContext(context).value(context); + + // Insert this context's offset + context_offsets_.insert(context, context_pos); + } + + // Remove from list so we don't process again + processing_filters.removeOne(context); + } + + level++; } - { - // 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()); - } + // Now that we've positioned all the contexts, position all other nodes relative to those contexts + for (Node *context : qAsConst(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) +NodeViewItem *NodeView::UpdateNodeItem(Node *node, bool ignore_own_context) { // Get UI item or create if it doesn't exist NodeViewItem *item = scene_.item_map().value(node); @@ -1606,7 +1573,11 @@ NodeViewItem *NodeView::UpdateNodeItem(Node *node) // 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_) { + for (Node *context : qAsConst(filter_nodes_)) { + if (context == node && ignore_own_context) { + continue; + } + if (graph_->GetNodesForContext(context).contains(node)) { QPointF this_context_pos = graph_->GetNodePosition(node, context); this_context_pos += context_offsets_.value(context); diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index a31f173c6..402c643d3 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -110,6 +110,8 @@ protected: virtual bool event(QEvent *event) override; + virtual bool eventFilter(QObject *object, QEvent *event) override; + private: void AttachNodesToCursor(const QVector &nodes); @@ -138,7 +140,7 @@ private: void CreateNewEdge(NodeViewItem *output_item); - NodeViewItem *UpdateNodeItem(Node *node); + NodeViewItem *UpdateNodeItem(Node *node, bool ignore_own_context = false); class NodeViewAttachNodesToCursor : public UndoCommand { @@ -228,13 +230,14 @@ private: FilterMode filter_mode_; QVector filter_nodes_; + QVector last_set_filter_nodes_; QMap context_offsets_; double scale_; bool create_edge_already_exists_; - QTimer reposition_contexts_timer_; + bool queue_reposition_contexts_; static const double kMinimumScale;