diff --git a/app/node/param.cpp b/app/node/param.cpp index eef504021..204cd5e43 100644 --- a/app/node/param.cpp +++ b/app/node/param.cpp @@ -87,7 +87,7 @@ bool NodeParam::AreDataTypesCompatible(const DataType &output_type, const QList< return false; } -void NodeParam::ConnectEdge(NodeOutput *output, NodeInput *input) +NodeEdgePtr NodeParam::ConnectEdge(NodeOutput *output, NodeInput *input) { // If the input can only accept one input (the default) and has one already, disconnect it if (!input->edges_.isEmpty() && !input->can_accept_multiple_inputs()) { @@ -98,6 +98,8 @@ void NodeParam::ConnectEdge(NodeOutput *output, NodeInput *input) output->edges_.append(edge); input->edges_.append(edge); + + return edge; } void NodeParam::DisconnectEdge(NodeEdgePtr edge) diff --git a/app/node/param.h b/app/node/param.h index a7efa8278..b06ea1078 100644 --- a/app/node/param.h +++ b/app/node/param.h @@ -34,8 +34,7 @@ class NodeParam : public QObject public: enum Type { kInput, - kOutput, - kBidirectional + kOutput }; enum DataType { @@ -69,7 +68,7 @@ public: static bool AreDataTypesCompatible(const DataType& output_type, const DataType& input_type); static bool AreDataTypesCompatible(const DataType& output_type, const QList& input_types); - static void ConnectEdge(NodeOutput *output, NodeInput *input); + static NodeEdgePtr ConnectEdge(NodeOutput *output, NodeInput *input); static void DisconnectEdge(NodeEdgePtr edge); static QString GetDefaultDataTypeName(const DataType &type); diff --git a/app/ui/style/olive-dark/style.css b/app/ui/style/olive-dark/style.css index 91b56c833..a4c14e326 100644 --- a/app/ui/style/olive-dark/style.css +++ b/app/ui/style/olive-dark/style.css @@ -68,6 +68,8 @@ QTreeView, QListView, QLineEdit, QMenu, QProgressBar, QPushButton::checked, Node background: #191919; } -NodeViewItemWidgetProxy { +/* Node styling */ +NodeViewItemWidget { qproperty-titlebarColor: #4040a0; + qproperty-borderColor: #000000; } diff --git a/app/ui/style/olive-light/style.css b/app/ui/style/olive-light/style.css index ae3e81385..b8771d162 100644 --- a/app/ui/style/olive-light/style.css +++ b/app/ui/style/olive-light/style.css @@ -68,6 +68,8 @@ QTreeView, QListView, QLineEdit, QMenu, QProgressBar, QPushButton::checked, Node background: #ffffff; } -NodeViewItemWidgetProxy { +/* Node styling */ +NodeViewItemWidget { qproperty-titlebarColor: #a0a0ff; -} + qproperty-borderColor: #000000; +} \ No newline at end of file diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index dc547a8ec..66b33d72e 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -26,6 +26,8 @@ NodeView::NodeView(QWidget *parent) : { setScene(&scene_); + setDragMode(RubberBandDrag); + connect(&scene_, SIGNAL(changed(const QList&)), this, SLOT(ItemsChanged())); } @@ -33,7 +35,6 @@ void NodeView::SetGraph(NodeGraph *graph) { // Clear the scene of all UI objects scene_.clear(); - edges_.clear(); // Set reference to the graph graph_ = graph; @@ -65,9 +66,6 @@ void NodeView::SetGraph(NodeGraph *graph) edge_ui->SetEdge(edge); scene_.addItem(edge_ui); - - // Keep track of edge widgets so they can be quickly updated whenever nodes move - edges_.append(edge_ui); } } } @@ -92,9 +90,32 @@ NodeViewItem *NodeView::NodeToUIObject(QGraphicsScene *scene, Node *n) return nullptr; } +NodeViewEdge *NodeView::EdgeToUIObject(QGraphicsScene *scene, NodeEdgePtr n) +{ + QList graphics_items = scene->items(); + + for (int i=0;i(graphics_items.at(i)); + + if (edge != nullptr) { + if (edge->edge() == n) { + return edge; + } + } + } + + return nullptr; +} + void NodeView::ItemsChanged() { - foreach (NodeViewEdge* edge, edges_) { - edge->Adjust(); + QList items = scene_.items(); + + foreach (QGraphicsItem* item, items) { + NodeViewEdge* edge = dynamic_cast(item); + + if (edge != nullptr) { + edge->Adjust(); + } } } diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index b198b31fa..cca154501 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -36,14 +36,13 @@ public: void SetGraph(NodeGraph* graph); static NodeViewItem* NodeToUIObject(QGraphicsScene* scene, Node* n); + static NodeViewEdge* EdgeToUIObject(QGraphicsScene* scene, NodeEdgePtr n); private: NodeGraph* graph_; QGraphicsScene scene_; - QList edges_; - private slots: void ItemsChanged(); diff --git a/app/widget/nodeview/nodeviewedge.cpp b/app/widget/nodeview/nodeviewedge.cpp index 8b3bba487..d65032ec3 100644 --- a/app/widget/nodeview/nodeviewedge.cpp +++ b/app/widget/nodeview/nodeviewedge.cpp @@ -21,21 +21,31 @@ #include "nodeviewedge.h" #include +#include #include "common/clamp.h" #include "common/lerp.h" #include "nodeview.h" +const int kNodeEdgeWidth = 2; + NodeViewEdge::NodeViewEdge(QGraphicsItem *parent) : QGraphicsLineItem(parent), - edge_(nullptr) + edge_(nullptr), + moving_(false), + connected_(false) { // Ensures this UI object is drawn behind other objects setZValue(-1); + + setAcceptHoverEvents(true); } void NodeViewEdge::SetEdge(NodeEdgePtr edge) { + SetMoving(false); + SetConnected(true); + // Set the new edge pointer edge_ = edge; @@ -43,9 +53,32 @@ void NodeViewEdge::SetEdge(NodeEdgePtr edge) Adjust(); } +NodeEdgePtr NodeViewEdge::edge() +{ + return edge_; +} + +qreal CalculateEdgeYPoint(NodeViewItem *item, int param_index, NodeViewItem *opposing) +{ + if (item->IsExpanded()) { + return item->pos().y() + item->GetParameterConnectorRect(param_index).center().y(); + } else { + qreal max_height = qMax(opposing->rect().height(), item->rect().height()); + + // Calculate the Y distance between the two nodes and create a 0.0-1.0 range for lerping + qreal input_value = clamp(0.5 + (opposing->pos().y() - item->pos().y()) / max_height / 4, 0.0, 1.0); + + // Use a lerp function to draw the line between the two corners + qreal input_y = item->pos().y() + lerp(0.0, item->rect().height(), input_value); + + // Set Y values according to calculations + return input_y; + } +} + void NodeViewEdge::Adjust() { - if (edge_ == nullptr || scene() == nullptr) { + if (edge_ == nullptr || scene() == nullptr || moving_) { return; } @@ -68,28 +101,28 @@ void NodeViewEdge::Adjust() )); } +void NodeViewEdge::SetMoving(bool m) +{ + moving_ = m; +} + +void NodeViewEdge::SetConnected(bool c) +{ + connected_ = c; +} + void NodeViewEdge::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { - setPen(QPen(widget->palette().color(QPalette::Text), 2)); + QPalette::ColorGroup color_mode; + + if (connected_) { + color_mode = QPalette::Active; + } else { + color_mode = QPalette::Disabled; + } + + setPen(QPen(widget->palette().color(color_mode, QPalette::Text), kNodeEdgeWidth)); QGraphicsLineItem::paint(painter, option, widget); } -qreal NodeViewEdge::CalculateEdgeYPoint(NodeViewItem *item, int param_index, NodeViewItem *opposing) -{ - if (item->IsExpanded()) { - return item->pos().y() + item->GetParameterConnectorRect(param_index).center().y(); - } else { - qreal max_height = qMax(opposing->rect().height(), item->rect().height()); - - // Calculate the Y distance between the two nodes and create a 0.0-1.0 range for lerping - qreal input_value = clamp(0.5 + (opposing->pos().y() - item->pos().y()) / max_height / 4, 0.0, 1.0); - - // Use a lerp function to draw the line between the two corners - qreal input_y = item->pos().y() + lerp(0.0, item->rect().height(), input_value); - - // Set Y values according to calculations - return input_y; - } -} - diff --git a/app/widget/nodeview/nodeviewedge.h b/app/widget/nodeview/nodeviewedge.h index b626ae988..847f75731 100644 --- a/app/widget/nodeview/nodeviewedge.h +++ b/app/widget/nodeview/nodeviewedge.h @@ -24,7 +24,6 @@ #include #include "node/edge.h" -#include "nodeviewitem.h" class NodeViewEdge : public QGraphicsLineItem { @@ -32,16 +31,21 @@ public: NodeViewEdge(QGraphicsItem* parent = nullptr); void SetEdge(NodeEdgePtr edge); + NodeEdgePtr edge(); void Adjust(); + void SetMoving(bool m); + void SetConnected(bool c); + protected: virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; private: - qreal CalculateEdgeYPoint(NodeViewItem* item, int param_index, NodeViewItem* opposing); - NodeEdgePtr edge_; + + bool moving_; + bool connected_; }; #endif // NODEEDGEITEM_H diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index b7dba5902..c3a8838f9 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -21,16 +21,18 @@ #include "nodeviewitem.h" #include +#include #include #include #include #include "core.h" +#include "nodeview.h" #include "ui/icons/icons.h" #include "window/mainwindow/mainwindow.h" const int kNodeViewItemBorderWidth = 2; -const int kNodeViewItemWidth = 250; +const int kNodeViewItemWidth = 200; const int kNodeViewItemTextPadding = 4; const int kNodeViewItemIconPadding = 12; @@ -38,6 +40,7 @@ NodeViewItem::NodeViewItem(QGraphicsItem *parent) : QGraphicsRectItem(parent), node_(nullptr), font_metrics(font), + dragging_edge_(nullptr), expanded_(false), standard_click_(false) { @@ -144,8 +147,7 @@ void NodeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti QPalette app_pal = olive::core.main_window()->palette(); // Set up border, which will change color if selected - // FIXME: Color not configurable? - QPen border_pen(Qt::black, kNodeViewItemBorderWidth); + QPen border_pen(obj_proxy_.BorderColor(), kNodeViewItemBorderWidth); QPen text_pen(app_pal.color(QPalette::Text)); @@ -254,9 +256,50 @@ void NodeViewItem::mousePressEvent(QGraphicsSceneMouseEvent *event) if (IsExpanded() // This is only possible if the node is expanded && node_ != nullptr) { // We can only loop through a node's parameters if a valid node is attached for (int i=0;iParameterCount();i++) { - if (GetParameterConnectorRect(i).contains(event->pos())) { + + if (GetParameterConnectorRect(i).contains(event->pos())) { // See if the cursor is in the rect + + NodeParam* param = node_->ParamAt(i); + + if (param->type() == NodeParam::kOutput || param->edges().isEmpty()) { + // For an output param (or an input param with no connections), we default to creating a new edge + + // Create new NodeViewEdge object for the user to create an edge + dragging_edge_ = new NodeViewEdge(); + dragging_edge_->SetMoving(true); + + drag_source_ = this; + drag_src_param_ = param; + + // Set the starting position to the current param's connector + dragging_edge_start_ = pos() + GetParameterConnectorRect(i).center(); + dragging_edge_->setLine(QLineF(dragging_edge_start_, dragging_edge_start_)); + + // Add it to the scene + scene()->addItem(dragging_edge_); + + } else if (param->type() == NodeParam::kInput) { + // For an input param, we default to moving an existing edge + // (here we use the last one, which will usually also be the first) + NodeEdgePtr edge = param->edges().last(); + + dragging_edge_ = NodeView::EdgeToUIObject(scene(), edge); + dragging_edge_->SetMoving(true); + + // The starting position will be the OPPOSING param's rect + NodeOutput* opposing_param = edge->output(); + Node* opposing_node = opposing_param->parent(); + NodeViewItem* opposing_node_view_item = NodeView::NodeToUIObject(scene(), opposing_node); + dragging_edge_start_ = opposing_node_view_item->pos() + opposing_node_view_item->GetParameterConnectorRect(opposing_param->index()).center(); + + drag_source_ = opposing_node_view_item; + drag_src_param_ = opposing_param; + + } + return; } + } } @@ -267,6 +310,45 @@ void NodeViewItem::mousePressEvent(QGraphicsSceneMouseEvent *event) void NodeViewItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { + // Check if an edge drag was initiated + if (dragging_edge_ != nullptr) { + QPointF end_point = event->scenePos(); + drag_dest_param_ = nullptr; + + // See if the mouse is currently inside a node + NodeViewItem* drop_item = dynamic_cast(scene()->itemAt(event->scenePos(), sceneTransform())); + if (drop_item != nullptr && drop_item != drag_source_) { + if (drop_item->IsExpanded()) { + drop_item->SetExpanded(true); + } + + // See if the mouse is currently inside a connector rect + for (int i=0;inode()->ParameterCount();i++) { + QRectF comp_rect = drop_item->GetParameterConnectorRect(i).adjusted(-node_connector_size_, + -node_connector_size_, + node_connector_size_, + node_connector_size_); + + NodeParam* comp_param = drop_item->node()->ParamAt(i); + + // If so, we snap inside it + if (comp_rect.contains(drop_item->mapFromScene(event->scenePos()))) { + + drag_dest_param_ = comp_param; + end_point = drop_item->mapToScene(drop_item->GetParameterConnectorRect(i).center()); + + break; + } + } + } + + dragging_edge_->SetConnected(drag_dest_param_ != nullptr); + + dragging_edge_->setLine(QLineF(dragging_edge_start_, end_point)); + + return; + } + if (standard_click_) { QGraphicsRectItem::mouseMoveEvent(event); } @@ -274,6 +356,38 @@ void NodeViewItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) void NodeViewItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { + // Check if an edge drag was initiated + if (dragging_edge_ != nullptr) { + + // FIXME: Make this undoable + + // If this edge had an edge, we should disconnect it now + if (dragging_edge_->edge() != nullptr) { + NodeParam::DisconnectEdge(dragging_edge_->edge()); + + dragging_edge_->SetEdge(nullptr); + } + + if (drag_dest_param_ == nullptr) { + // If we didn't drag to anywhere, just get rid of this edge + scene()->removeItem(dragging_edge_); + } else { + // If we did, create a new edge now + NodeEdgePtr new_edge; + + if (drag_dest_param_->type() == NodeParam::kOutput) { + new_edge = NodeParam::ConnectEdge(static_cast(drag_dest_param_), static_cast(drag_src_param_)); + } else { + new_edge = NodeParam::ConnectEdge(static_cast(drag_src_param_), static_cast(drag_dest_param_)); + } + + dragging_edge_->SetEdge(new_edge); + } + + dragging_edge_ = nullptr; + return; + } + // Check if we clicked the Expand/Collapse icon if (expand_hitbox_.contains(event->pos())) { SetExpanded(!IsExpanded()); @@ -284,16 +398,26 @@ void NodeViewItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) } } -NodeViewItemWidgetProxy::NodeViewItemWidgetProxy() +NodeViewItemWidget::NodeViewItemWidget() { } -QColor NodeViewItemWidgetProxy::TitleBarColor() +QColor NodeViewItemWidget::TitleBarColor() { return title_bar_color_; } -void NodeViewItemWidgetProxy::SetTitleBarColor(QColor color) +void NodeViewItemWidget::SetTitleBarColor(QColor color) { title_bar_color_ = color; } + +QColor NodeViewItemWidget::BorderColor() +{ + return border_color_; +} + +void NodeViewItemWidget::SetBorderColor(QColor color) +{ + border_color_ = color; +} diff --git a/app/widget/nodeview/nodeviewitem.h b/app/widget/nodeview/nodeviewitem.h index 46db61c90..c72f8c2b9 100644 --- a/app/widget/nodeview/nodeviewitem.h +++ b/app/widget/nodeview/nodeviewitem.h @@ -27,17 +27,26 @@ #include #include "node/node.h" +#include "nodeviewedge.h" -class NodeViewItemWidgetProxy : public QWidget { +/** + * @brief A proxy object to allow NodeViewItem access to CSS functions + */ +class NodeViewItemWidget : public QWidget { Q_OBJECT Q_PROPERTY(QColor titlebarColor READ TitleBarColor WRITE SetTitleBarColor DESIGNABLE true) + Q_PROPERTY(QColor borderColor READ BorderColor WRITE SetBorderColor DESIGNABLE true) public: - NodeViewItemWidgetProxy(); + NodeViewItemWidget(); QColor TitleBarColor(); void SetTitleBarColor(QColor color); + + QColor BorderColor(); + void SetBorderColor(QColor color); private: QColor title_bar_color_; + QColor border_color_; }; class NodeViewItem : public QGraphicsRectItem @@ -67,7 +76,7 @@ private: Node* node_; - NodeViewItemWidgetProxy obj_proxy_; + NodeViewItemWidget obj_proxy_; QRectF title_bar_rect_; @@ -77,6 +86,13 @@ private: QFontMetrics font_metrics; + // Edge dragging variables + NodeViewEdge* dragging_edge_; + QPointF dragging_edge_start_; + NodeParam* drag_src_param_; + NodeParam* drag_dest_param_; + NodeViewItem* drag_source_; + int node_connector_size_; bool expanded_;