From 2473bccec3d8c18ebadfb87ab528a1a3cf5c506f Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 1 Dec 2021 12:14:26 -0800 Subject: [PATCH] implemented arrays and reimplemented expanding --- app/widget/nodeview/nodeview.cpp | 84 ++++++++- app/widget/nodeview/nodeview.h | 10 +- app/widget/nodeview/nodeviewitem.cpp | 262 +++++++++++++++++---------- app/widget/nodeview/nodeviewitem.h | 14 +- 4 files changed, 266 insertions(+), 104 deletions(-) diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 2dab4cb29..5f87c40b4 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -49,7 +49,6 @@ NodeView::NodeView(QWidget *parent) : create_edge_(nullptr), create_edge_output_item_(nullptr), create_edge_input_item_(nullptr), - create_edge_expand_item_(nullptr), paste_command_(nullptr), scale_(1.0) { @@ -500,11 +499,11 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) // Clear highlight if we set one create_edge_input_item_->SetHighlighted(false); - // Collapse if we expanded it - if (create_edge_expand_item_) { - create_edge_expand_item_->SetExpanded(false); - create_edge_expand_item_->setZValue(0); + // Collapse any items we expanded + for (auto it=create_edge_expanded_items_.crbegin(); it!=create_edge_expanded_items_.crend(); it++) { + CollapseItem(*it); } + create_edge_expanded_items_.clear(); NodeInput &creating_input = create_edge_input_; if (creating_input.IsValid()) { @@ -526,6 +525,13 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) } command->add_child(new NodeEdgeAddCommand(creating_output, creating_input)); + + // If the output is not in the input's context, add it now. We check the item rather than + // the node itself, because sometimes a node may not be in the context but another node + // representing it will be (e.g. groups) + if (!scene_.context_map().value(create_edge_input_item_->GetContext())->GetItemFromMap(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(); @@ -624,6 +630,18 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) super::mouseReleaseEvent(event); } +void NodeView::mouseDoubleClickEvent(QMouseEvent *event) +{ + super::mouseDoubleClickEvent(event); + + if (!(event->modifiers() & Qt::ControlModifier)) { + NodeViewItem *item_at_cursor = dynamic_cast(itemAt(event->pos())); + if (item_at_cursor) { + item_at_cursor->ToggleExpanded(); + } + } +} + void NodeView::resizeEvent(QResizeEvent *event) { super::resizeEvent(event); @@ -1022,6 +1040,13 @@ void NodeView::ZoomFromKeyboard(double multiplier) ZoomIntoCursorPosition(nullptr, multiplier, cursor_pos); } +void NodeView::ClearCreateEdgeInputIfNecessary() +{ + if (create_edge_from_output_ && create_edge_input_.IsValid()) { + create_edge_input_.Reset(); + } +} + QPointF NodeView::GetEstimatedPositionForContext(NodeViewItem *item, Node *context) const { return item->GetNodePosition() - context_offsets_.value(context); @@ -1051,6 +1076,37 @@ void NodeView::PositionNewEdge(const QPoint &pos) item_at_cursor = nullptr; } + // Collapse any items that the cursor is no longer inside + int i=create_edge_expanded_items_.size() - 1; + for ( ; i>=0; i--) { + NodeViewItem* nvi = create_edge_expanded_items_.at(i); + QPointF local_pt = nvi->mapFromScene(scene_pt); + + if (nvi->contains(local_pt) || (!nvi->IsOutputItem() && nvi->parentItem()->contains(nvi->parentItem()->mapFromScene(scene_pt)) && local_pt.y() > nvi->rect().bottom())) { + break; + } else { + // Collapsing an item will destroy its children, so if the cursor item happens to be a child + // of the item we're about to collapse, set it to null + if (item_at_cursor && item_at_cursor->parentItem() == nvi) { + item_at_cursor = nullptr; + } + + if (opposing_item && opposing_item->parentItem() == nvi) { + opposing_item = nullptr; + ClearCreateEdgeInputIfNecessary(); + } + + CollapseItem(nvi); + } + } + create_edge_expanded_items_.resize(i + 1); + + // Expand item if possible + if (item_at_cursor && item_at_cursor->CanBeExpanded() && !item_at_cursor->IsExpanded()) { + ExpandItem(item_at_cursor); + create_edge_expanded_items_.append(item_at_cursor); + } + // 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)) @@ -1068,11 +1124,7 @@ void NodeView::PositionNewEdge(const QPoint &pos) } // Clear cached input - if (create_edge_from_output_) { - if (create_edge_input_.IsValid()) { - create_edge_input_.Reset(); - } - } + ClearCreateEdgeInputIfNecessary(); // If this is an input and we're opposing_item = item_at_cursor; @@ -1293,4 +1345,16 @@ bool NodeView::IsItemAttachedToCursor(NodeViewItem *item) const return false; } +void NodeView::ExpandItem(NodeViewItem *item) +{ + item->SetExpanded(true); + item->setZValue(100); +} + +void NodeView::CollapseItem(NodeViewItem *item) +{ + item->SetExpanded(false); + item->setZValue(0); +} + } diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index 4b3a0417f..8e34bba99 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -107,6 +107,7 @@ protected: virtual void mousePressEvent(QMouseEvent *event) override; virtual void mouseMoveEvent(QMouseEvent *event) override; virtual void mouseReleaseEvent(QMouseEvent* event) override; + virtual void mouseDoubleClickEvent(QMouseEvent* event) override; virtual void resizeEvent(QResizeEvent *event) override; @@ -135,6 +136,8 @@ private: void ZoomFromKeyboard(double multiplier); + void ClearCreateEdgeInputIfNecessary(); + QPointF GetEstimatedPositionForContext(NodeViewItem *item, Node *context) const; Menu *CreateAddMenu(Menu *parent); @@ -149,6 +152,10 @@ private: bool IsItemAttachedToCursor(NodeViewItem *item) const; + void ExpandItem(NodeViewItem *item); + + void CollapseItem(NodeViewItem *item); + NodeViewMiniMap *minimap_; struct AttachedItem { @@ -164,11 +171,12 @@ 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_already_exists_; bool create_edge_from_output_; + QVector create_edge_expanded_items_; + NodeViewScene scene_; MultiUndoCommand* paste_command_; diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 9357a8f0b..d6bb565e9 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -48,6 +48,7 @@ NodeViewItem::NodeViewItem(Node *node, const QString &input, int element, Node * expanded_(false), highlighted_(false), flow_dir_(NodeViewCommon::kInvalidDirection), + arrow_click_(false), label_as_output_(false) { // @@ -235,9 +236,7 @@ void NodeViewItem::RemoveEdge(NodeViewEdge *edge) void NodeViewItem::SetExpanded(bool e, bool hide_titlebar) { - if ((IsOutputItem() && !has_connectable_inputs_) - || (!IsOutputItem() && !(node_->GetInputFlags(input_) & kInputFlagArray)) - || (expanded_ == e)) { + if (!CanBeExpanded() || (expanded_ == e)) { return; } @@ -253,13 +252,14 @@ void NodeViewItem::SetExpanded(bool e, bool hide_titlebar) } if (expanded_) { + node_->Retranslate(); + 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++; } @@ -271,15 +271,12 @@ void NodeViewItem::SetExpanded(bool e, bool hide_titlebar) (*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; } @@ -299,10 +296,10 @@ void NodeViewItem::SetExpanded(bool e, bool hide_titlebar) delete child; } children_.clear(); - - SetRectSize(1); } + UpdateChildrenPositions(); + if (flow_dir_ == NodeViewCommon::kTopToBottom) { UpdateOutputConnectorPosition(); } @@ -325,47 +322,81 @@ void NodeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti // has been slightly modified QPalette app_pal = Core::instance()->main_window()->palette(); - // Draw the titlebar - if (IsOutputItem()) { - QRectF single_unit_rect = rect(); - single_unit_rect.setHeight(DefaultItemHeight()); + // We only draw a single unit's worth + QRectF single_unit_rect = rect(); + single_unit_rect.setHeight(DefaultItemHeight()); - // Output item drawing code + if (IsOutputItem()) { + // Set output item colors painter->setPen(Qt::black); painter->setBrush(node_->brush(single_unit_rect.top(), single_unit_rect.bottom())); + } else { + // Set input item colors + painter->setPen(Qt::NoPen); + painter->setBrush(element_ == -1 ? app_pal.color(QPalette::Window) : app_pal.color(QPalette::Base)); + } - painter->drawRect(single_unit_rect); + painter->drawRect(single_unit_rect); - painter->setPen(app_pal.color(QPalette::Text)); + // Draw highlight if applicable + if (highlighted_) { + QColor highlight_col = app_pal.color(QPalette::Text); + highlight_col.setAlpha(64); + painter->setBrush(highlight_col); + painter->drawRect(rect()); + } - QString node_label, node_shortname; + // Determine what text to draw and whether to draw an arrow + QString node_label, node_name; + if (IsOutputItem()) { if (label_as_output_) { - node_shortname = QCoreApplication::translate("NodeViewItem", "Output"); + node_name = QCoreApplication::translate("NodeViewItem", "Output"); } else { node_label = node_->GetLabel(); - node_shortname = node_->ShortName(); + node_name = node_->ShortName(); } - - int icon_size = painter->fontMetrics().height()/2; - - if (node_label.isEmpty()) { - // Draw shortname only - DrawNodeTitle(painter, node_shortname, single_unit_rect, Qt::AlignVCenter, icon_size, has_connectable_inputs_); + } else { + if (element_ == -1) { + node_name = node_->GetInputName(input_); } else { - int text_pad = DefaultTextPadding()/2; - 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, has_connectable_inputs_); - f.setPointSizeF(font_sz * 0.6); - painter->setFont(f); - DrawNodeTitle(painter, node_shortname, safe_label_bounds, Qt::AlignBottom, icon_size, false); + node_name = QString::number(element_); } + } - // Draw final border + // Draw arrow if necessary + int arrow_size = CanBeExpanded() ? DrawExpandArrow(painter) : 0; + + if (IsOutputItem()) { + // Determine the text color (automatically calculate from node background color) + painter->setPen(ColorCoding::GetUISelectorColor(node_->color())); + } else { + // Just use text item + painter->setPen(app_pal.text().color()); + } + + if (node_label.isEmpty()) { + // Draw name only + DrawNodeTitle(painter, node_name, single_unit_rect, Qt::AlignVCenter, arrow_size); + } else { + int text_pad = DefaultTextPadding()/2; + QRectF safe_label_bounds = single_unit_rect.adjusted(text_pad, text_pad, -text_pad, -text_pad); + QFont f; + qreal font_sz = f.pointSizeF(); + + // Draw label as larger/upper text + f.setPointSizeF(font_sz * 0.8); + painter->setFont(f); + DrawNodeTitle(painter, node_label, safe_label_bounds, Qt::AlignTop, arrow_size); + + // Draw node name as smaller/lower text + f.setPointSizeF(font_sz * 0.6); + painter->setFont(f); + DrawNodeTitle(painter, node_name, safe_label_bounds, Qt::AlignBottom, arrow_size); + } + + // Draw final border (output only) + if (IsOutputItem()) { QPen border_pen; border_pen.setWidth(node_border_width_); @@ -379,28 +410,17 @@ void NodeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti painter->setBrush(Qt::NoBrush); painter->drawRect(rect()); - } else { - // 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_)); } } void NodeViewItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { + if (last_arrow_rect_.contains(event->pos().toPoint())) { + arrow_click_ = true; + ToggleExpanded(); + return; + } + event->setModifiers(FlipControlAndShiftModifiers(event->modifiers())); QGraphicsRectItem::mousePressEvent(event); @@ -408,6 +428,10 @@ void NodeViewItem::mousePressEvent(QGraphicsSceneMouseEvent *event) void NodeViewItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { + if (arrow_click_) { + return; + } + event->setModifiers(FlipControlAndShiftModifiers(event->modifiers())); QGraphicsRectItem::mouseMoveEvent(event); @@ -415,20 +439,16 @@ void NodeViewItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) void NodeViewItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { + if (arrow_click_) { + arrow_click_ = false; + return; + } + event->setModifiers(FlipControlAndShiftModifiers(event->modifiers())); QGraphicsRectItem::mouseReleaseEvent(event); } -void NodeViewItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) -{ - QGraphicsRectItem::mouseDoubleClickEvent(event); - - if (!(event->modifiers() & Qt::ControlModifier)) { - SetExpanded(!IsExpanded()); - } -} - QVariant NodeViewItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) { if (change == ItemPositionHasChanged && node_) { @@ -456,29 +476,22 @@ void NodeViewItem::ReadjustAllEdges() void NodeViewItem::UpdateContextRect() { - if (NodeViewContext *ctx = dynamic_cast(parentItem())) { - ctx->UpdateRect(); + QGraphicsItem *item = parentItem(); + + while (item) { + if (NodeViewContext *ctx = dynamic_cast(item)) { + ctx->UpdateRect(); + break; + } + + item = item->parentItem(); } } -void NodeViewItem::DrawNodeTitle(QPainter* painter, QString text, const QRectF& rect, Qt::Alignment vertical_align, int icon_size, bool draw_arrow) +void NodeViewItem::DrawNodeTitle(QPainter* painter, QString text, const QRectF& rect, Qt::Alignment vertical_align, int icon_full_size) { QFontMetrics fm = painter->fontMetrics(); - painter->setRenderHint(QPainter::SmoothPixmapTransform); - - // Draw right or down arrow based on expanded state - 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(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 = this->rect().width(); int max_text_width = item_width - DefaultTextPadding() * 2 - icon_full_size; @@ -497,9 +510,6 @@ void NodeViewItem::DrawNodeTitle(QPainter* painter, QString text, const QRectF& text = concatenated; } - // Determine the text color (automatically calculate from node background color) - painter->setPen(ColorCoding::GetUISelectorColor(node_->color())); - // Determine X position (favors horizontal centering unless it'll overrun the arrow) QRectF text_rect = rect; Qt::Alignment text_align = Qt::AlignHCenter | vertical_align; @@ -515,6 +525,28 @@ void NodeViewItem::DrawNodeTitle(QPainter* painter, QString text, const QRectF& text); } +int NodeViewItem::DrawExpandArrow(QPainter *painter) +{ + // Draw right or down arrow based on expanded state + int icon_size = painter->fontMetrics().height()/2; + int icon_padding = DefaultItemHeight() / 2 - icon_size / 2; + int icon_full_size = icon_size + icon_padding * 2; + + painter->setRenderHint(QPainter::SmoothPixmapTransform); + + const QIcon& expand_icon = IsExpanded() ? icon::TriDown : icon::TriRight; + int icon_size_scaled = icon_size * painter->transform().m11(); + + last_arrow_rect_ = QRect(this->rect().x() + icon_padding, + this->rect().y() + icon_padding, + icon_size, + icon_size); + + painter->drawPixmap(last_arrow_rect_, expand_icon.pixmap(QSize(icon_size_scaled, icon_size_scaled))); + + return icon_full_size; +} + void NodeViewItem::SetLabelAsOutput(bool e) { label_as_output_ = e; @@ -642,6 +674,44 @@ void NodeViewItem::SetRectSize(int height_units) setRect(QRectF(-widget_width/2, -widget_height/2, widget_width, widget_height * height_units)); } +bool NodeViewItem::CanBeExpanded() const +{ + if (IsOutputItem()) { + return has_connectable_inputs_; + } else { + return node_->GetInputFlags(input_) & kInputFlagArray && element_ == -1 && !node_->IsInputConnected(input_); + } +} + +void NodeViewItem::UpdateChildrenPositions() +{ + int y = 1; + int h = DefaultItemHeight(); + + foreach (NodeViewItem *c, children_) { + c->setPos(QPointF(0, y * h)); + + y += c->GetLogicalHeightWithChildren(); + } + + SetRectSize(y); + + if (NodeViewItem *p = dynamic_cast(parentItem())) { + p->UpdateChildrenPositions(); + } +} + +int NodeViewItem::GetLogicalHeightWithChildren() const +{ + int h = 1; + + foreach (NodeViewItem *c, children_) { + h += c->GetLogicalHeightWithChildren(); + } + + return h; +} + void NodeViewItem::UpdateFlowDirectionOfInputItem(NodeViewItem *child) { if (!child->IsOutputItem()) { @@ -659,16 +729,26 @@ void NodeViewItem::UpdateFlowDirectionOfInputItem(NodeViewItem *child) void NodeViewItem::RepopulateInputs() { - has_connectable_inputs_ = false; + if (IsOutputItem()) { + has_connectable_inputs_ = false; - foreach (const QString& input, node_->inputs()) { - if (IsInputValid(input)) { - has_connectable_inputs_ = true; - break; + foreach (const QString& input, node_->inputs()) { + if (IsInputValid(input)) { + has_connectable_inputs_ = true; + break; + } + } + + input_connector_->setVisible(has_connectable_inputs_); + + if (IsExpanded()) { + // Create or remove inputs when necessary + } + } else { + if (IsExpanded() && element_ == -1) { + // Create or remove array elements when necessary } } - - input_connector_->setVisible(has_connectable_inputs_); } void NodeViewItem::NodeAppearanceChanged() @@ -698,13 +778,13 @@ NodeViewItem *NodeViewItem::GetItemForInput(NodeInput input) // Look for the input in our children foreach (NodeViewItem *i, children_) { if (i->input_ == input.input()) { - return i; + return i->GetItemForInput(input); } } } else { // Look for element in our children if (input.element() >= 0 && input.element() < children_.size()) { - return children_.at(input.element()); + return children_.at(input.element())->GetItemForInput(input); } } } diff --git a/app/widget/nodeview/nodeviewitem.h b/app/widget/nodeview/nodeviewitem.h index 4b2c7236f..bbd6ec395 100644 --- a/app/widget/nodeview/nodeviewitem.h +++ b/app/widget/nodeview/nodeviewitem.h @@ -143,20 +143,23 @@ public: void UpdateFlowDirectionOfInputItem(NodeViewItem *child); + bool CanBeExpanded() const; + protected: virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override; virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; - virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override; virtual QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override; private: void UpdateContextRect(); - void DrawNodeTitle(QPainter *painter, QString text, const QRectF &rect, Qt::Alignment vertical_align, int icon_size, bool draw_arrow); + void DrawNodeTitle(QPainter *painter, QString text, const QRectF &rect, Qt::Alignment vertical_align, int icon_full_size); + + int DrawExpandArrow(QPainter *painter); /** * @brief Internal update function when logical position changes @@ -170,6 +173,10 @@ private: void SetRectSize(int height_units = 1); + void UpdateChildrenPositions(); + + int GetLogicalHeightWithChildren() const; + /** * @brief Reference to attached Node */ @@ -200,6 +207,9 @@ private: QPointF cached_node_pos_; + QRect last_arrow_rect_; + bool arrow_click_; + NodeViewItemConnector *input_connector_; NodeViewItemConnector *output_connector_;