nodeview: improved code structures
This commit is contained in:
@@ -24,6 +24,8 @@ set(OLIVE_SOURCES
|
||||
widget/nodeview/nodeviewitem.cpp
|
||||
widget/nodeview/nodeviewitemwidgetproxy.h
|
||||
widget/nodeview/nodeviewitemwidgetproxy.cpp
|
||||
widget/nodeview/nodeviewscene.h
|
||||
widget/nodeview/nodeviewscene.cpp
|
||||
widget/nodeview/nodeviewundo.h
|
||||
widget/nodeview/nodeviewundo.cpp
|
||||
PARENT_SCOPE
|
||||
|
||||
@@ -39,8 +39,6 @@ NodeView::NodeView(QWidget *parent) :
|
||||
connect(&scene_, &QGraphicsScene::changed, this, &NodeView::ItemsChanged);
|
||||
connect(&scene_, &QGraphicsScene::selectionChanged, this, &NodeView::SceneSelectionChangedSlot);
|
||||
connect(this, &NodeView::customContextMenuRequested, this, &NodeView::ShowContextMenu);
|
||||
connect(&reorganize_timer_, &QTimer::timeout, &reorganize_timer_, &QTimer::stop);
|
||||
connect(&reorganize_timer_, &QTimer::timeout, this, &NodeView::Reorganize);
|
||||
|
||||
setMouseTracking(true);
|
||||
}
|
||||
@@ -58,10 +56,10 @@ void NodeView::SetGraph(NodeGraph *graph)
|
||||
}
|
||||
|
||||
if (graph_ != nullptr) {
|
||||
disconnect(graph_, &NodeGraph::NodeAdded, this, &NodeView::AddNode);
|
||||
disconnect(graph_, &NodeGraph::NodeRemoved, this, &NodeView::RemoveNode);
|
||||
disconnect(graph_, &NodeGraph::EdgeAdded, this, &NodeView::AddEdge);
|
||||
disconnect(graph_, &NodeGraph::EdgeRemoved, this, &NodeView::RemoveEdge);
|
||||
disconnect(graph_, &NodeGraph::NodeAdded, &scene_, &NodeViewScene::AddNode);
|
||||
disconnect(graph_, &NodeGraph::NodeRemoved, &scene_, &NodeViewScene::RemoveNode);
|
||||
disconnect(graph_, &NodeGraph::EdgeAdded, &scene_, &NodeViewScene::AddEdge);
|
||||
disconnect(graph_, &NodeGraph::EdgeRemoved, &scene_, &NodeViewScene::RemoveEdge);
|
||||
}
|
||||
|
||||
// Clear the scene of all UI objects
|
||||
@@ -69,66 +67,23 @@ void NodeView::SetGraph(NodeGraph *graph)
|
||||
|
||||
// Set reference to the graph
|
||||
graph_ = graph;
|
||||
scene_.SetGraph(graph_);
|
||||
|
||||
// If the graph is valid, add UI objects for each of its Nodes
|
||||
if (graph_ != nullptr) {
|
||||
connect(graph_, &NodeGraph::NodeAdded, this, &NodeView::AddNode);
|
||||
connect(graph_, &NodeGraph::NodeRemoved, this, &NodeView::RemoveNode);
|
||||
connect(graph_, &NodeGraph::EdgeAdded, this, &NodeView::AddEdge);
|
||||
connect(graph_, &NodeGraph::EdgeRemoved, this, &NodeView::RemoveEdge);
|
||||
connect(graph_, &NodeGraph::NodeAdded, &scene_, &NodeViewScene::AddNode);
|
||||
connect(graph_, &NodeGraph::NodeRemoved, &scene_, &NodeViewScene::RemoveNode);
|
||||
connect(graph_, &NodeGraph::EdgeAdded, &scene_, &NodeViewScene::AddEdge);
|
||||
connect(graph_, &NodeGraph::EdgeRemoved, &scene_, &NodeViewScene::RemoveEdge);
|
||||
|
||||
QList<Node*> graph_nodes = graph_->nodes();
|
||||
|
||||
foreach (Node* node, graph_nodes) {
|
||||
AddNode(node);
|
||||
scene_.AddNode(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NodeViewItem *NodeView::NodeToUIObject(QGraphicsScene *scene, Node *n)
|
||||
{
|
||||
QList<QGraphicsItem*> graphics_items = scene->items();
|
||||
|
||||
for (int i=0;i<graphics_items.size();i++) {
|
||||
NodeViewItem* item = dynamic_cast<NodeViewItem*>(graphics_items.at(i));
|
||||
|
||||
if (item != nullptr) {
|
||||
if (item->node() == n) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
NodeViewEdge *NodeView::EdgeToUIObject(QGraphicsScene *scene, NodeEdgePtr n)
|
||||
{
|
||||
QList<QGraphicsItem*> graphics_items = scene->items();
|
||||
|
||||
for (int i=0;i<graphics_items.size();i++) {
|
||||
NodeViewEdge* edge = dynamic_cast<NodeViewEdge*>(graphics_items.at(i));
|
||||
|
||||
if (edge != nullptr) {
|
||||
if (edge->edge() == n) {
|
||||
return edge;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
NodeViewItem *NodeView::NodeToUIObject(Node *n)
|
||||
{
|
||||
return NodeToUIObject(&scene_, n);
|
||||
}
|
||||
|
||||
NodeViewEdge *NodeView::EdgeToUIObject(NodeEdgePtr n)
|
||||
{
|
||||
return EdgeToUIObject(&scene_, n);
|
||||
}
|
||||
|
||||
void NodeView::DeleteSelected()
|
||||
{
|
||||
if (!graph_) {
|
||||
@@ -176,7 +131,7 @@ void NodeView::Select(const QList<Node *> &nodes)
|
||||
DeselectAll();
|
||||
|
||||
foreach (Node* n, nodes) {
|
||||
NodeViewItem* item = NodeToUIObject(n);
|
||||
NodeViewItem* item = scene_.NodeToUIObject(n);
|
||||
|
||||
if (item) {
|
||||
item->setSelected(true);
|
||||
@@ -191,7 +146,7 @@ void NodeView::SelectWithDependencies(const QList<Node *> &nodes)
|
||||
QList<Node*> nodes_with_deps;
|
||||
|
||||
foreach (Node* n, nodes) {
|
||||
NodeViewItem* item = NodeToUIObject(n);
|
||||
NodeViewItem* item = scene_.NodeToUIObject(n);
|
||||
|
||||
if (item) {
|
||||
item->setSelected(true);
|
||||
@@ -200,7 +155,7 @@ void NodeView::SelectWithDependencies(const QList<Node *> &nodes)
|
||||
QList<Node*> deps = n->GetDependencies();
|
||||
|
||||
foreach (Node* d, deps) {
|
||||
item = NodeToUIObject(d);
|
||||
item = scene_.NodeToUIObject(d);
|
||||
|
||||
if (item) {
|
||||
item->setSelected(true);
|
||||
@@ -209,56 +164,6 @@ void NodeView::SelectWithDependencies(const QList<Node *> &nodes)
|
||||
}
|
||||
}
|
||||
|
||||
void NodeView::AddNode(Node* node)
|
||||
{
|
||||
NodeViewItem* item = new NodeViewItem();
|
||||
|
||||
item->SetNode(node);
|
||||
|
||||
scene_.addItem(item);
|
||||
|
||||
// Add a NodeViewEdge for each connection
|
||||
foreach (NodeParam* param, node->parameters()) {
|
||||
|
||||
// We only bother working with outputs since eventually this will cover all inputs too
|
||||
// (covering both would lead to duplicates since every edge connects to one input and one output)
|
||||
if (param->type() == NodeParam::kOutput) {
|
||||
const QVector<NodeEdgePtr>& edges = param->edges();
|
||||
|
||||
foreach(NodeEdgePtr edge, edges) {
|
||||
AddEdge(edge);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QueueReorganize();
|
||||
}
|
||||
|
||||
void NodeView::RemoveNode(Node *node)
|
||||
{
|
||||
NodeViewItem* item = NodeToUIObject(scene(), node);
|
||||
|
||||
delete item;
|
||||
}
|
||||
|
||||
void NodeView::AddEdge(NodeEdgePtr edge)
|
||||
{
|
||||
NodeViewEdge* edge_ui = new NodeViewEdge();
|
||||
|
||||
edge_ui->SetEdge(edge);
|
||||
|
||||
scene_.addItem(edge_ui);
|
||||
|
||||
QueueReorganize();
|
||||
}
|
||||
|
||||
void NodeView::RemoveEdge(NodeEdgePtr edge)
|
||||
{
|
||||
NodeViewEdge* edge_ui = EdgeToUIObject(scene(), edge);
|
||||
|
||||
delete edge_ui;
|
||||
}
|
||||
|
||||
void NodeView::ItemsChanged()
|
||||
{
|
||||
QList<QGraphicsItem*> items = scene_.items();
|
||||
@@ -416,7 +321,7 @@ void NodeView::CreateNodeSlot(QAction *action)
|
||||
if (new_node) {
|
||||
Core::instance()->undo_stack()->push(new NodeAddCommand(graph_, new_node));
|
||||
|
||||
NodeViewItem* item = NodeToUIObject(new_node);
|
||||
NodeViewItem* item = scene_.NodeToUIObject(new_node);
|
||||
AttachItemToCursor(item);
|
||||
}
|
||||
}
|
||||
@@ -555,115 +460,6 @@ void NodeView::PlaceNode(NodeViewItem *n, const QPointF &pos)
|
||||
}
|
||||
}
|
||||
|
||||
QList<Node *> NodeView::GetNodeDirectDescendants(Node* n, const QList<Node*> connected_nodes, QList<Node*>& processed_nodes)
|
||||
{
|
||||
QList<Node*> direct_descendants = connected_nodes;
|
||||
|
||||
processed_nodes.append(n);
|
||||
|
||||
// Remove any nodes that aren't necessarily attached directly
|
||||
for (int i=0;i<direct_descendants.size();i++) {
|
||||
Node* connected = direct_descendants.at(i);
|
||||
|
||||
for (int j=1;j<connected->output()->edges().size();j++) {
|
||||
Node* this_output_connection = connected->output()->edges().at(j)->input()->parentNode();
|
||||
if (!processed_nodes.contains(this_output_connection)) {
|
||||
direct_descendants.removeAt(i);
|
||||
i--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return direct_descendants;
|
||||
}
|
||||
|
||||
int NodeView::FindWeightsInternal(Node *node, QHash<Node *, int> &weights, QList<Node*>& weighted_nodes)
|
||||
{
|
||||
QList<Node*> connected_nodes = node->GetImmediateDependencies();
|
||||
|
||||
int weight = 0;
|
||||
|
||||
if (!connected_nodes.isEmpty()) {
|
||||
QList<Node*> direct_descendants = GetNodeDirectDescendants(node, connected_nodes, weighted_nodes);
|
||||
|
||||
foreach (Node* dep, direct_descendants) {
|
||||
weight += FindWeightsInternal(dep, weights, weighted_nodes);
|
||||
}
|
||||
}
|
||||
|
||||
weight = qMax(weight, 1);
|
||||
|
||||
weights.insert(node, weight);
|
||||
|
||||
return weight;
|
||||
}
|
||||
|
||||
void NodeView::ReorganizeInternal(NodeViewItem* src_item, QHash<Node*, int>& weights, QList<Node*>& positioned_nodes)
|
||||
{
|
||||
Node* n = src_item->node();
|
||||
|
||||
QList<Node*> connected_nodes = n->GetImmediateDependencies();
|
||||
|
||||
if (connected_nodes.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QList<Node*> direct_descendants = GetNodeDirectDescendants(n, connected_nodes, positioned_nodes);
|
||||
|
||||
int descendant_weight = 0;
|
||||
foreach (Node* dep, direct_descendants) {
|
||||
descendant_weight += weights.value(dep);
|
||||
}
|
||||
|
||||
qreal center_y = src_item->y();
|
||||
qreal total_height = descendant_weight * src_item->rect().height() + (direct_descendants.size()-1) * src_item->rect().height()/2;
|
||||
double item_top = center_y - (total_height/2) + src_item->rect().height()/2;
|
||||
|
||||
// Set each node's position
|
||||
int weight_index = 0;
|
||||
for (int i=0;i<direct_descendants.size();i++) {
|
||||
Node* connected = direct_descendants.at(i);
|
||||
|
||||
NodeViewItem* item = NodeToUIObject(connected);
|
||||
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
|
||||
double item_y = item_top;
|
||||
|
||||
// Multiply the index by the item height (with 1.5 for padding)
|
||||
item_y += weight_index * src_item->rect().height() * 1.5;
|
||||
|
||||
QPointF item_pos(src_item->pos().x() - item->rect().width() * 3 / 2,
|
||||
item_y);
|
||||
|
||||
item->setPos(item_pos);
|
||||
|
||||
weight_index += weights.value(connected);
|
||||
}
|
||||
|
||||
// Recursively work on each node
|
||||
foreach (Node* connected, connected_nodes) {
|
||||
NodeViewItem* item = NodeToUIObject(connected);
|
||||
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ReorganizeInternal(item, weights, positioned_nodes);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeView::QueueReorganize()
|
||||
{
|
||||
// Avoids the fairly complex Reorganize() function every single time a connection or node is added
|
||||
|
||||
reorganize_timer_.stop();
|
||||
reorganize_timer_.start(20);
|
||||
}
|
||||
|
||||
void NodeView::AttachItemToCursor(NodeViewItem *item)
|
||||
{
|
||||
attached_item_ = item;
|
||||
@@ -675,32 +471,3 @@ void NodeView::DetachItemFromCursor()
|
||||
{
|
||||
AttachItemToCursor(nullptr);
|
||||
}
|
||||
|
||||
void NodeView::Reorganize()
|
||||
{
|
||||
if (!graph_) {
|
||||
return;
|
||||
}
|
||||
|
||||
QList<Node*> end_nodes;
|
||||
|
||||
// Calculate the nodes that don't output to anything, they'll be our anchors
|
||||
foreach (Node* node, graph_->nodes()) {
|
||||
if (!node->HasConnectedOutputs()) {
|
||||
end_nodes.append(node);
|
||||
}
|
||||
}
|
||||
|
||||
QList<Node*> processed_nodes;
|
||||
|
||||
QHash<Node*, int> node_weights;
|
||||
foreach (Node* end_node, end_nodes) {
|
||||
FindWeightsInternal(end_node, node_weights, processed_nodes);
|
||||
}
|
||||
|
||||
processed_nodes.clear();
|
||||
|
||||
foreach (Node* end_node, end_nodes) {
|
||||
ReorganizeInternal(NodeToUIObject(end_node), node_weights, processed_nodes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,7 @@
|
||||
#include <QTimer>
|
||||
|
||||
#include "node/graph.h"
|
||||
#include "widget/nodeview/nodeviewedge.h"
|
||||
#include "widget/nodeview/nodeviewitem.h"
|
||||
#include "nodeviewscene.h"
|
||||
|
||||
/**
|
||||
* @brief A widget for viewing and editing node graphs
|
||||
@@ -47,36 +46,6 @@ public:
|
||||
*/
|
||||
void SetGraph(NodeGraph* graph);
|
||||
|
||||
/**
|
||||
* @brief Retrieve the graphical widget corresponding to a specific Node
|
||||
*
|
||||
* In situations where you know what Node you're working with but need the UI object (e.g. for positioning), this
|
||||
* static function will retrieve the NodeViewItem (Node UI representation) connected to this Node in a certain
|
||||
* QGraphicsScene. This can be called from any other UI object, since it'll have a reference to the QGraphicsScene
|
||||
* through QGraphicsItem::scene().
|
||||
*
|
||||
* If the scene does not contain a widget for this node (usually meaning the node's graph is not the active graph
|
||||
* in this view/scene), this function returns nullptr.
|
||||
*/
|
||||
static NodeViewItem* NodeToUIObject(QGraphicsScene* scene, Node* n);
|
||||
|
||||
/**
|
||||
* @brief Retrieve the graphical widget corresponding to a specific NodeEdge
|
||||
*
|
||||
* Same as NodeToUIObject() but returns a NodeViewEdge corresponding to a NodeEdgePtr instead.
|
||||
*/
|
||||
static NodeViewEdge* EdgeToUIObject(QGraphicsScene* scene, NodeEdgePtr n);
|
||||
|
||||
/**
|
||||
* @brief Overloaded NodeToUIObject(QGraphicsScene* scene, Node* n) if you have direct access to a NodeView instance
|
||||
*/
|
||||
NodeViewItem* NodeToUIObject(Node* n);
|
||||
|
||||
/**
|
||||
* @brief Overloaded EdgeToUIObject(QGraphicsScene* scene, NodeEdgePtr n) if you have direct access to a NodeView instance
|
||||
*/
|
||||
NodeViewEdge* EdgeToUIObject(NodeEdgePtr n);
|
||||
|
||||
/**
|
||||
* @brief Delete selected nodes from graph (user-friendly/undoable)
|
||||
*/
|
||||
@@ -102,16 +71,8 @@ protected:
|
||||
virtual void mouseMoveEvent(QMouseEvent *event) override;
|
||||
|
||||
private:
|
||||
QList<Node*> GetNodeDirectDescendants(Node* n, const QList<Node*> connected_nodes, QList<Node*>& processed_nodes);
|
||||
|
||||
void PlaceNode(NodeViewItem* n, const QPointF& pos);
|
||||
|
||||
int FindWeightsInternal(Node* node, QHash<Node*, int>& weights, QList<Node*>& weighted_nodes);
|
||||
|
||||
void ReorganizeInternal(NodeViewItem *src_item, QHash<Node*, int>& weights, QList<Node *> &positioned_nodes);
|
||||
|
||||
void QueueReorganize();
|
||||
|
||||
void AttachItemToCursor(NodeViewItem* item);
|
||||
|
||||
void DetachItemFromCursor();
|
||||
@@ -123,43 +84,9 @@ private:
|
||||
NodeViewEdge* drop_edge_;
|
||||
NodeInput* drop_compatible_input_;
|
||||
|
||||
QGraphicsScene scene_;
|
||||
|
||||
QTimer reorganize_timer_;
|
||||
NodeViewScene scene_;
|
||||
|
||||
private slots:
|
||||
/**
|
||||
* @brief Slot when a Node is added to a graph (SetGraph() connects this)
|
||||
*
|
||||
* This should NEVER be called directly, only connected to a NodeGraph. To add a Node to the NodeGraph
|
||||
* use NodeGraph::AddNode().
|
||||
*/
|
||||
void AddNode(Node* node);
|
||||
|
||||
/**
|
||||
* @brief Slot when a Node is removed from a graph (SetGraph() connects this)
|
||||
*
|
||||
* This should NEVER be called directly, only connected to a NodeGraph. To remove a Node from the NodeGraph
|
||||
* use NodeGraph::RemoveNode().
|
||||
*/
|
||||
void RemoveNode(Node* node);
|
||||
|
||||
/**
|
||||
* @brief Slot when an edge is added to a graph (SetGraph() connects this)
|
||||
*
|
||||
* This should NEVER be called directly, only connected to a NodeGraph. To add an edge (i.e. connect two node
|
||||
* parameters together), use NodeParam::ConnectEdge().
|
||||
*/
|
||||
void AddEdge(NodeEdgePtr edge);
|
||||
|
||||
/**
|
||||
* @brief Slot when an edge is removed from a graph (SetGraph() connects this)
|
||||
*
|
||||
* This should NEVER be called directly, only connected to a NodeGraph. To remove an edge (i.e. disconnect two node
|
||||
* parameters), use NodeParam::DisconnectEdge().
|
||||
*/
|
||||
void RemoveEdge(NodeEdgePtr edge);
|
||||
|
||||
/**
|
||||
* @brief Internal function triggered when any change is signalled from the QGraphicsScene
|
||||
*
|
||||
@@ -182,11 +109,6 @@ private slots:
|
||||
*/
|
||||
void CreateNodeSlot(QAction* action);
|
||||
|
||||
/**
|
||||
* @brief Automatically reposition the nodes based on their connections
|
||||
*/
|
||||
void Reorganize();
|
||||
|
||||
};
|
||||
|
||||
#endif // NODEVIEW_H
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "common/clamp.h"
|
||||
#include "common/lerp.h"
|
||||
#include "nodeview.h"
|
||||
#include "nodeviewscene.h"
|
||||
|
||||
NodeViewEdge::NodeViewEdge(QGraphicsItem *parent) :
|
||||
QGraphicsPathItem(parent),
|
||||
@@ -77,13 +78,17 @@ qreal CalculateEdgeYPoint(NodeViewItem *item, NodeParam* param, NodeViewItem *op
|
||||
|
||||
void NodeViewEdge::Adjust()
|
||||
{
|
||||
if (edge_ == nullptr || scene() == nullptr) {
|
||||
if (!edge_ || !scene()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the UI objects of both nodes that this edge connects
|
||||
NodeViewItem* output = NodeView::NodeToUIObject(scene(), edge_->output()->parentNode());
|
||||
NodeViewItem* input = NodeView::NodeToUIObject(scene(), edge_->input()->parentNode());
|
||||
NodeViewItem* output = static_cast<NodeViewScene*>(scene())->NodeToUIObject(edge_->output()->parentNode());
|
||||
NodeViewItem* input = static_cast<NodeViewScene*>(scene())->NodeToUIObject(edge_->input()->parentNode());
|
||||
|
||||
if (!output || !input) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create initial values
|
||||
QPointF output_point = QPointF(output->pos().x() + output->rect().left() + output->rect().width(), 0);
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "common/qtutils.h"
|
||||
#include "core.h"
|
||||
#include "nodeview.h"
|
||||
#include "nodeviewscene.h"
|
||||
#include "nodeviewundo.h"
|
||||
#include "ui/icons/icons.h"
|
||||
#include "window/mainwindow/mainwindow.h"
|
||||
@@ -326,12 +327,12 @@ void NodeViewItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
||||
drag_src_param_ = edge->output();
|
||||
|
||||
// Get its Node UI object
|
||||
drag_source_ = NodeView::NodeToUIObject(scene(), drag_src_param_->parentNode());
|
||||
drag_source_ = static_cast<NodeViewScene*>(scene())->NodeToUIObject(drag_src_param_->parentNode());
|
||||
|
||||
// Get the opposing parameter's rect center using the line's current coordinates
|
||||
// (we use the current coordinates because a complex formula is used for the line's coords if the opposing
|
||||
// node is collapsed, therefore it's easier to just retrieve it from line itself)
|
||||
NodeViewEdge* existing_edge_ui = NodeView::EdgeToUIObject(scene(), edge);
|
||||
NodeViewEdge* existing_edge_ui = static_cast<NodeViewScene*>(scene())->EdgeToUIObject(edge);
|
||||
QPainterPath existing_edge_line = existing_edge_ui->path();
|
||||
QPointF edge_start = existing_edge_line.pointAtPercent(0);
|
||||
QPointF edge_end = existing_edge_line.pointAtPercent(1);
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
#include "nodeviewscene.h"
|
||||
|
||||
NodeViewScene::NodeViewScene(QObject *parent) :
|
||||
QGraphicsScene(parent),
|
||||
graph_(nullptr)
|
||||
{
|
||||
connect(&reorganize_timer_, &QTimer::timeout, &reorganize_timer_, &QTimer::stop);
|
||||
connect(&reorganize_timer_, &QTimer::timeout, this, &NodeViewScene::Reorganize);
|
||||
}
|
||||
|
||||
void NodeViewScene::clear()
|
||||
{
|
||||
{
|
||||
QHash<Node*, NodeViewItem*>::const_iterator i;
|
||||
for (i=item_map_.begin();i!=item_map_.end();i++) {
|
||||
delete i.value();
|
||||
}
|
||||
item_map_.clear();
|
||||
}
|
||||
|
||||
{
|
||||
QHash<NodeEdge*, NodeViewEdge*>::const_iterator i;
|
||||
for (i=edge_map_.begin();i!=edge_map_.end();i++) {
|
||||
delete i.value();
|
||||
}
|
||||
edge_map_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
NodeViewItem *NodeViewScene::NodeToUIObject(Node *n)
|
||||
{
|
||||
return item_map_.value(n);
|
||||
}
|
||||
|
||||
NodeViewEdge *NodeViewScene::EdgeToUIObject(NodeEdgePtr n)
|
||||
{
|
||||
return edge_map_.value(n.get());
|
||||
}
|
||||
|
||||
void NodeViewScene::SetGraph(NodeGraph *graph)
|
||||
{
|
||||
graph_ = graph;
|
||||
}
|
||||
|
||||
void NodeViewScene::AddNode(Node* node)
|
||||
{
|
||||
NodeViewItem* item = new NodeViewItem();
|
||||
|
||||
item->SetNode(node);
|
||||
|
||||
addItem(item);
|
||||
item_map_.insert(node, item);
|
||||
|
||||
// Add a NodeViewEdge for each connection
|
||||
foreach (NodeParam* param, node->parameters()) {
|
||||
|
||||
// We only bother working with outputs since eventually this will cover all inputs too
|
||||
// (covering both would lead to duplicates since every edge connects to one input and one output)
|
||||
if (param->type() == NodeParam::kOutput) {
|
||||
const QVector<NodeEdgePtr>& edges = param->edges();
|
||||
|
||||
foreach(NodeEdgePtr edge, edges) {
|
||||
AddEdge(edge);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QueueReorganize();
|
||||
}
|
||||
|
||||
void NodeViewScene::RemoveNode(Node *node)
|
||||
{
|
||||
delete item_map_.take(node);
|
||||
}
|
||||
|
||||
void NodeViewScene::AddEdge(NodeEdgePtr edge)
|
||||
{
|
||||
NodeViewEdge* edge_ui = new NodeViewEdge();
|
||||
|
||||
edge_ui->SetEdge(edge);
|
||||
|
||||
addItem(edge_ui);
|
||||
edge_map_.insert(edge.get(), edge_ui);
|
||||
|
||||
QueueReorganize();
|
||||
}
|
||||
|
||||
void NodeViewScene::RemoveEdge(NodeEdgePtr edge)
|
||||
{
|
||||
delete edge_map_.take(edge.get());
|
||||
}
|
||||
|
||||
void NodeViewScene::QueueReorganize()
|
||||
{
|
||||
// Avoids the fairly complex Reorganize() function every single time a connection or node is added
|
||||
|
||||
reorganize_timer_.stop();
|
||||
reorganize_timer_.start(20);
|
||||
}
|
||||
|
||||
QList<Node *> NodeViewScene::GetNodeDirectDescendants(Node* n, const QList<Node*> connected_nodes, QList<Node*>& processed_nodes)
|
||||
{
|
||||
QList<Node*> direct_descendants = connected_nodes;
|
||||
|
||||
processed_nodes.append(n);
|
||||
|
||||
// Remove any nodes that aren't necessarily attached directly
|
||||
for (int i=0;i<direct_descendants.size();i++) {
|
||||
Node* connected = direct_descendants.at(i);
|
||||
|
||||
for (int j=1;j<connected->output()->edges().size();j++) {
|
||||
Node* this_output_connection = connected->output()->edges().at(j)->input()->parentNode();
|
||||
if (!processed_nodes.contains(this_output_connection)) {
|
||||
direct_descendants.removeAt(i);
|
||||
i--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return direct_descendants;
|
||||
}
|
||||
|
||||
int NodeViewScene::FindWeightsInternal(Node *node, QHash<Node *, int> &weights, QList<Node*>& weighted_nodes)
|
||||
{
|
||||
QList<Node*> connected_nodes = node->GetImmediateDependencies();
|
||||
|
||||
int weight = 0;
|
||||
|
||||
if (!connected_nodes.isEmpty()) {
|
||||
QList<Node*> direct_descendants = GetNodeDirectDescendants(node, connected_nodes, weighted_nodes);
|
||||
|
||||
foreach (Node* dep, direct_descendants) {
|
||||
weight += FindWeightsInternal(dep, weights, weighted_nodes);
|
||||
}
|
||||
}
|
||||
|
||||
weight = qMax(weight, 1);
|
||||
|
||||
weights.insert(node, weight);
|
||||
|
||||
return weight;
|
||||
}
|
||||
|
||||
void NodeViewScene::ReorganizeInternal(NodeViewItem* src_item, QHash<Node*, int>& weights, QList<Node*>& positioned_nodes)
|
||||
{
|
||||
if (!src_item) {
|
||||
return;
|
||||
}
|
||||
|
||||
Node* n = src_item->node();
|
||||
|
||||
QList<Node*> connected_nodes = n->GetImmediateDependencies();
|
||||
|
||||
if (connected_nodes.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QList<Node*> direct_descendants = GetNodeDirectDescendants(n, connected_nodes, positioned_nodes);
|
||||
|
||||
int descendant_weight = 0;
|
||||
foreach (Node* dep, direct_descendants) {
|
||||
descendant_weight += weights.value(dep);
|
||||
}
|
||||
|
||||
qreal center_y = src_item->y();
|
||||
qreal total_height = descendant_weight * src_item->rect().height() + (direct_descendants.size()-1) * src_item->rect().height()/2;
|
||||
double item_top = center_y - (total_height/2) + src_item->rect().height()/2;
|
||||
|
||||
// Set each node's position
|
||||
int weight_index = 0;
|
||||
for (int i=0;i<direct_descendants.size();i++) {
|
||||
Node* connected = direct_descendants.at(i);
|
||||
|
||||
NodeViewItem* item = NodeToUIObject(connected);
|
||||
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
|
||||
double item_y = item_top;
|
||||
|
||||
// Multiply the index by the item height (with 1.5 for padding)
|
||||
item_y += weight_index * src_item->rect().height() * 1.5;
|
||||
|
||||
QPointF item_pos(src_item->pos().x() - item->rect().width() * 3 / 2,
|
||||
item_y);
|
||||
|
||||
item->setPos(item_pos);
|
||||
|
||||
weight_index += weights.value(connected);
|
||||
}
|
||||
|
||||
// Recursively work on each node
|
||||
foreach (Node* connected, connected_nodes) {
|
||||
NodeViewItem* item = NodeToUIObject(connected);
|
||||
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ReorganizeInternal(item, weights, positioned_nodes);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeViewScene::Reorganize()
|
||||
{
|
||||
if (!graph_) {
|
||||
return;
|
||||
}
|
||||
|
||||
QList<Node*> end_nodes;
|
||||
|
||||
// Calculate the nodes that don't output to anything, they'll be our anchors
|
||||
foreach (Node* node, graph_->nodes()) {
|
||||
if (!node->HasConnectedOutputs()) {
|
||||
end_nodes.append(node);
|
||||
}
|
||||
}
|
||||
|
||||
QList<Node*> processed_nodes;
|
||||
|
||||
QHash<Node*, int> node_weights;
|
||||
foreach (Node* end_node, end_nodes) {
|
||||
FindWeightsInternal(end_node, node_weights, processed_nodes);
|
||||
}
|
||||
|
||||
processed_nodes.clear();
|
||||
|
||||
foreach (Node* end_node, end_nodes) {
|
||||
ReorganizeInternal(NodeToUIObject(end_node), node_weights, processed_nodes);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
#ifndef NODEVIEWSCENE_H
|
||||
#define NODEVIEWSCENE_H
|
||||
|
||||
#include <QGraphicsScene>
|
||||
#include <QTimer>
|
||||
|
||||
#include "node/graph.h"
|
||||
#include "widget/nodeview/nodeviewedge.h"
|
||||
#include "widget/nodeview/nodeviewitem.h"
|
||||
|
||||
class NodeViewScene : public QGraphicsScene
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
NodeViewScene(QObject *parent = nullptr);
|
||||
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* @brief Retrieve the graphical widget corresponding to a specific Node
|
||||
*
|
||||
* In situations where you know what Node you're working with but need the UI object (e.g. for positioning), this
|
||||
* static function will retrieve the NodeViewItem (Node UI representation) connected to this Node in a certain
|
||||
* QGraphicsScene. This can be called from any other UI object, since it'll have a reference to the QGraphicsScene
|
||||
* through QGraphicsItem::scene().
|
||||
*
|
||||
* If the scene does not contain a widget for this node (usually meaning the node's graph is not the active graph
|
||||
* in this view/scene), this function returns nullptr.
|
||||
*/
|
||||
NodeViewItem* NodeToUIObject(Node* n);
|
||||
|
||||
/**
|
||||
* @brief Retrieve the graphical widget corresponding to a specific NodeEdge
|
||||
*
|
||||
* Same as NodeToUIObject() but returns a NodeViewEdge corresponding to a NodeEdgePtr instead.
|
||||
*/
|
||||
NodeViewEdge* EdgeToUIObject(NodeEdgePtr n);
|
||||
|
||||
void SetGraph(NodeGraph* graph);
|
||||
|
||||
public slots:
|
||||
/**
|
||||
* @brief Slot when a Node is added to a graph (SetGraph() connects this)
|
||||
*
|
||||
* This should NEVER be called directly, only connected to a NodeGraph. To add a Node to the NodeGraph
|
||||
* use NodeGraph::AddNode().
|
||||
*/
|
||||
void AddNode(Node* node);
|
||||
|
||||
/**
|
||||
* @brief Slot when a Node is removed from a graph (SetGraph() connects this)
|
||||
*
|
||||
* This should NEVER be called directly, only connected to a NodeGraph. To remove a Node from the NodeGraph
|
||||
* use NodeGraph::RemoveNode().
|
||||
*/
|
||||
void RemoveNode(Node* node);
|
||||
|
||||
/**
|
||||
* @brief Slot when an edge is added to a graph (SetGraph() connects this)
|
||||
*
|
||||
* This should NEVER be called directly, only connected to a NodeGraph. To add an edge (i.e. connect two node
|
||||
* parameters together), use NodeParam::ConnectEdge().
|
||||
*/
|
||||
void AddEdge(NodeEdgePtr edge);
|
||||
|
||||
/**
|
||||
* @brief Slot when an edge is removed from a graph (SetGraph() connects this)
|
||||
*
|
||||
* This should NEVER be called directly, only connected to a NodeGraph. To remove an edge (i.e. disconnect two node
|
||||
* parameters), use NodeParam::DisconnectEdge().
|
||||
*/
|
||||
void RemoveEdge(NodeEdgePtr edge);
|
||||
|
||||
private:
|
||||
void QueueReorganize();
|
||||
|
||||
QList<Node*> GetNodeDirectDescendants(Node* n, const QList<Node*> connected_nodes, QList<Node*>& processed_nodes);
|
||||
|
||||
int FindWeightsInternal(Node* node, QHash<Node*, int>& weights, QList<Node*>& weighted_nodes);
|
||||
|
||||
void ReorganizeInternal(NodeViewItem *src_item, QHash<Node*, int>& weights, QList<Node *> &positioned_nodes);
|
||||
|
||||
QHash<Node*, NodeViewItem*> item_map_;
|
||||
|
||||
QHash<NodeEdge*, NodeViewEdge*> edge_map_;
|
||||
|
||||
QTimer reorganize_timer_;
|
||||
|
||||
NodeGraph* graph_;
|
||||
|
||||
private slots:
|
||||
/**
|
||||
* @brief Automatically reposition the nodes based on their connections
|
||||
*/
|
||||
void Reorganize();
|
||||
|
||||
};
|
||||
|
||||
#endif // NODEVIEWSCENE_H
|
||||
Reference in New Issue
Block a user