diff --git a/src/lib/component/controller/helper/BucketLayouter.cpp b/src/lib/component/controller/helper/BucketLayouter.cpp index 093c8f5a..1a5d723c 100644 --- a/src/lib/component/controller/helper/BucketLayouter.cpp +++ b/src/lib/component/controller/helper/BucketLayouter.cpp @@ -55,7 +55,7 @@ const DummyNode::BundledNodesSet& Bucket::getNodes() const return m_nodes; } -void Bucket::preLayout(Vec2i viewSize) +void Bucket::preLayout(Vec2i viewSize, bool addVerticalOffset) { int cols = (viewSize.y > 0 ? (m_height / viewSize.y) : 0) + 1; @@ -66,37 +66,137 @@ void Bucket::preLayout(Vec2i viewSize) m_height = 0; + std::vector colWidths; + std::vector colHeights; + std::vector> nodesInCol; + nodesInCol.push_back({ }); + for (const std::shared_ptr& node : m_nodes) { + if (y > height) + { + colHeights.push_back(y - GraphViewStyle::s_gridCellPadding); + colWidths.push_back(width); + + y = 0; + x += GraphViewStyle::toGridOffset(width + 45); + width = 0; + + nodesInCol.push_back({ }); + } + node->position.x = x; node->position.y = y; + nodesInCol.back().push_back(node.get()); + y += GraphViewStyle::toGridSize(node->size.y) + GraphViewStyle::s_gridCellPadding; width = (node->size.x > width ? node->size.x : width); m_height = (y > m_height ? y : m_height); + } - if (y > height) + colHeights.push_back(y - GraphViewStyle::s_gridCellPadding); + colWidths.push_back(width); + + m_width = x + width; + m_height -= GraphViewStyle::s_gridCellPadding; + + for (size_t i = 0; i < nodesInCol.size(); i++) + { + for (DummyNode* node : nodesInCol[i]) { - y = 0; - - x += GraphViewStyle::toGridOffset(width + 30); - width = 0; + node->columnSize.x = colWidths[i]; + node->columnSize.y = colHeights[i]; } } - m_width = x + width; + if (!addVerticalOffset || nodesInCol.size() < 2) + { + return; + } + + std::vector aboveNodes; + std::vector belowNodes; + + // align each column vertically and leave a gap in the middle where edges can pass through + // NOTE: m_height is not gonna be correct after this, but stays unchanged to allow correct positioning next to + // active node. + for (size_t i = 0; i < nodesInCol.size(); i++) + { + int offset = 0; + bool hasOffset = false; + int mid = colHeights[i] / 2; + + for (DummyNode* node : nodesInCol[i]) + { + if (hasOffset) + { + belowNodes.push_back(node); + } + else if (nodesInCol[i].size() == 1) + { + offset -= (node->size.y + GraphViewStyle::s_gridCellPadding) / 2; + aboveNodes.push_back(node); + } + else if (node->position.y < mid && node->position.y + node->size.y > mid) + { + if (mid - node->position.y < (node->position.y + node->size.y) - mid) + { + offset = mid - node->position.y + GraphViewStyle::s_gridCellPadding / 2; + belowNodes.push_back(node); + } + else + { + offset = mid - (node->position.y + node->size.y) - GraphViewStyle::s_gridCellPadding / 2; + aboveNodes.push_back(node); + } + hasOffset = true; + } + else if (node->position.y + node->size.y < mid && + mid < node->position.y + node->size.y + GraphViewStyle::s_gridCellPadding) + { + offset = mid - (node->position.y + node->size.y + GraphViewStyle::s_gridCellPadding / 2); + aboveNodes.push_back(node); + hasOffset = true; + } + else + { + aboveNodes.push_back(node); + } + } + + offset += (m_height - colHeights[i]) / 2; + for (DummyNode* node : nodesInCol[i]) + { + node->position.y() += offset; + } + } + + int nodeOffset = GraphViewStyle::s_gridCellPadding + GraphViewStyle::s_gridCellSize; + for (DummyNode* node : aboveNodes) + { + node->position.y() -= nodeOffset; + } + for (DummyNode* node : belowNodes) + { + node->position.y() += nodeOffset; + } } void Bucket::layout(int x, int y, int width, int height) { - int cx = GraphViewStyle::toGridOffset(x + (width - m_width) / 2); - int cy = GraphViewStyle::toGridOffset(y + (height - m_height) / 2); + if (!m_nodes.size()) + { + return; + } + + Vec2i offset = Vec2i(x + (width - m_width) / 2, y + (height - m_height) / 2); + offset = GraphViewStyle::alignOnRaster((*m_nodes.begin())->position + offset) - (*m_nodes.begin())->position; for (const std::shared_ptr& node : m_nodes) { - node->position.x = node->position.x + cx; - node->position.y = node->position.y + cy; + node->position += offset; } } @@ -126,6 +226,7 @@ void BucketLayouter::createBuckets( { addNode(node); activeNodeAdded = true; + m_activeParentNode = node.get(); } } @@ -205,7 +306,7 @@ void BucketLayouter::layoutBuckets(bool addVerticalOffset) { Bucket* bucket = &m_buckets[j][i]; - bucket->preLayout(m_viewSize); + bucket->preLayout(m_viewSize, i != 0); std::map::iterator wt = widths.find(i); if (wt == widths.end() || wt->second < bucket->getWidth()) @@ -221,6 +322,13 @@ void BucketLayouter::layoutBuckets(bool addVerticalOffset) } } + int verticalOffset = 0; + if (m_activeParentNode) + { + Vec4i rect = m_activeParentNode->getActiveSubNodeRect(); + verticalOffset = (rect.y + rect.w - m_activeParentNode->size.y) / 2; + } + int y = 0; for (int j = m_j1; j <= m_j2; j++) { @@ -239,7 +347,12 @@ void BucketLayouter::layoutBuckets(bool addVerticalOffset) // move every second bucket in a row slighly lower to avoid edges over nodes else if (addVerticalOffset && std::abs(i) % 2 == 1) { - yOff += GraphViewStyle::toGridGap(10); + yOff = GraphViewStyle::toGridGap(10); + } + // align buckets left and right of center to be vertically centered next to the active node + else if (j == 0 && i != 0 && verticalOffset != 0) + { + yOff = verticalOffset; } bucket->layout(x, y + yOff, widths[i], heights[j]); @@ -290,7 +403,7 @@ std::shared_ptr BucketLayouter::findTopMostDummyNodeRecursive( void BucketLayouter::addNode(std::shared_ptr node) { - Bucket* bucket = getBucket(node->layoutBucket.x, node->layoutBucket.y); + Bucket* bucket = getBucket(0, 0); bucket->addNode(node); } diff --git a/src/lib/component/controller/helper/BucketLayouter.h b/src/lib/component/controller/helper/BucketLayouter.h index 98cbfc0d..b1ad7538 100644 --- a/src/lib/component/controller/helper/BucketLayouter.h +++ b/src/lib/component/controller/helper/BucketLayouter.h @@ -23,7 +23,7 @@ public: void addNode(std::shared_ptr node); const DummyNode::BundledNodesSet& getNodes() const; - void preLayout(Vec2i viewSize); + void preLayout(Vec2i viewSize, bool addVerticalOffset); void layout(int x, int y, int width, int height); int i; @@ -65,6 +65,8 @@ private: int m_j1; int m_i2; int m_j2; + + DummyNode* m_activeParentNode = nullptr; }; #endif // BUCKET_LAYOUTER_H diff --git a/src/lib/component/controller/helper/DummyNode.h b/src/lib/component/controller/helper/DummyNode.h index ab444cb7..8bfc0072 100644 --- a/src/lib/component/controller/helper/DummyNode.h +++ b/src/lib/component/controller/helper/DummyNode.h @@ -2,6 +2,7 @@ #define DUMMY_NODE_H #include "utility/math/Vector2.h" +#include "utility/math/Vector4.h" #include "utility/types.h" #include "utility/utility.h" #include "utility/utilityString.h" @@ -103,7 +104,6 @@ public: , accessKind(ACCESS_NONE) , invisibleSubNodeCount(0) , bundleId(0) - , layoutBucket(0, 0) , bundledNodeCount(0) , bundledNodeType(NodeType::NODE_SYMBOL) , qualifierName(NAME_DELIMITER_UNKNOWN) @@ -184,6 +184,27 @@ public: return false; } + Vec4i getActiveSubNodeRect(Vec2i pos = Vec2i()) const + { + pos += position; + + if (active) + { + return Vec4i(pos.x, pos.y, pos.x + size.x, pos.y + size.y); + } + + for (const std::shared_ptr& node : subNodes) + { + Vec4i rect = node->getActiveSubNodeRect(pos); + if (rect.w() > 0) + { + return rect; + } + } + + return Vec4i(); + } + size_t getActiveSubNodeCount() const { size_t count = 0; @@ -444,7 +465,7 @@ public: Id bundleId; // Layout - Vec2i layoutBucket; + Vec2i columnSize; // BundleNode BundledNodesSet bundledNodes; diff --git a/src/lib/component/view/GraphViewStyle.cpp b/src/lib/component/view/GraphViewStyle.cpp index 1e94b36f..537eb82a 100644 --- a/src/lib/component/view/GraphViewStyle.cpp +++ b/src/lib/component/view/GraphViewStyle.cpp @@ -628,8 +628,8 @@ GraphViewStyle::EdgeStyle GraphViewStyle::getStyleForEdgeType( style.originOffset.x = 17; style.targetOffset.x = 17; - style.originOffset.y = -1; - style.targetOffset.y = 1; + style.originOffset.y = 1; + style.targetOffset.y = -1; style.color = getEdgeColor(utility::encodeToUtf8(Edge::getUnderscoredTypeString(type)), isActive || isFocused); @@ -647,8 +647,8 @@ GraphViewStyle::EdgeStyle GraphViewStyle::getStyleForEdgeType( style.zValue = isActive ? 1 : -5; break; case Edge::EDGE_CALL: - style.originOffset.y = 1; - style.targetOffset.y = -1; + style.originOffset.y = 3; + style.targetOffset.y = -3; style.verticalOffset = 4; if (isTrailEdge && isActive) @@ -659,8 +659,8 @@ GraphViewStyle::EdgeStyle GraphViewStyle::getStyleForEdgeType( } break; case Edge::EDGE_USAGE: - style.originOffset.y = 3; - style.targetOffset.y = -3; + style.originOffset.y = 5; + style.targetOffset.y = -5; style.verticalOffset = 6; break; case Edge::EDGE_INHERITANCE: @@ -669,8 +669,8 @@ GraphViewStyle::EdgeStyle GraphViewStyle::getStyleForEdgeType( style.arrowClosed = true; style.originOffset.x = 7; style.targetOffset.x = 34; - style.originOffset.y = -15; - style.targetOffset.y = 15; + style.originOffset.y = 10; + style.targetOffset.y = -10; style.verticalOffset = 0; style.cornerRadius = 7; style.zValue = isActive ? 2 : -3; @@ -688,6 +688,11 @@ GraphViewStyle::EdgeStyle GraphViewStyle::getStyleForEdgeType( style.arrowLength = 10; style.arrowWidth = 13; style.arrowClosed = true; + case Edge::EDGE_TEMPLATE_ARGUMENT: + case Edge::EDGE_TYPE_ARGUMENT: + case Edge::EDGE_TEMPLATE_DEFAULT_ARGUMENT: + style.originOffset.y = 5; + style.targetOffset.y = -5; break; case Edge::EDGE_INCLUDE: @@ -701,38 +706,23 @@ GraphViewStyle::EdgeStyle GraphViewStyle::getStyleForEdgeType( int GraphViewStyle::toGridOffset(int x) { - if (x < 1) + if (x > 0) { - return 0; + return std::ceil(x / double(s_gridCellPadding + s_gridCellSize)) * (s_gridCellPadding + s_gridCellSize); } - - int r = 0; - - while (r < x - 1) + else { - r += s_gridCellPadding + s_gridCellSize; + return std::floor(x / double(s_gridCellPadding + s_gridCellSize)) * (s_gridCellPadding + s_gridCellSize); } - - return r; } int GraphViewStyle::toGridSize(int x) { - if (x < 1) - { - return 0; - } - return s_gridCellSize + toGridOffset(x - s_gridCellSize); } int GraphViewStyle::toGridGap(int x) { - if (x < 1) - { - return 0; - } - return s_gridCellPadding + toGridOffset(x - s_gridCellPadding); } diff --git a/src/lib_gui/qt/graphics/QtLineItemBase.cpp b/src/lib_gui/qt/graphics/QtLineItemBase.cpp index bc27cd40..05fedfac 100644 --- a/src/lib_gui/qt/graphics/QtLineItemBase.cpp +++ b/src/lib_gui/qt/graphics/QtLineItemBase.cpp @@ -10,7 +10,6 @@ QtLineItemBase::QtLineItemBase(QGraphicsItem* parent) , m_onBack(false) , m_earlyBend(false) , m_route(ROUTE_ANY) - , m_pivot(PIVOT_THIRD) { this->setAcceptHoverEvents(true); } @@ -49,11 +48,6 @@ void QtLineItemBase::setRoute(Route route) m_route = route; } -void QtLineItemBase::setPivot(Pivot pivot) -{ - m_pivot = pivot; -} - void QtLineItemBase::setOnFront(bool front) { m_onFront = front; @@ -406,7 +400,7 @@ void QtLineItemBase::drawArrow(const QPolygon& poly, QPainterPath* path, QPainte void QtLineItemBase::getPivotPoints(Vec2f* p, const Vec4i& in, const Vec4i& out, int offset, bool target) const { - float f = m_pivot == PIVOT_THIRD ? (target ? 2 / 3.f : 1 / 3.f) : 1 / 2.f; + float f = 1 / 2.f; p[0] = Vec2f(in.x + (in.z - in.x) * f + offset, out.y); p[2] = Vec2f(in.x + (in.z - in.x) * f + offset, out.w); diff --git a/src/lib_gui/qt/graphics/QtLineItemBase.h b/src/lib_gui/qt/graphics/QtLineItemBase.h index aeed706f..4da511b6 100644 --- a/src/lib_gui/qt/graphics/QtLineItemBase.h +++ b/src/lib_gui/qt/graphics/QtLineItemBase.h @@ -18,12 +18,6 @@ public: ROUTE_VERTICAL }; - enum Pivot - { - PIVOT_THIRD, - PIVOT_MIDDLE - }; - QtLineItemBase(QGraphicsItem* parent); virtual ~QtLineItemBase(); @@ -34,7 +28,6 @@ public: size_t weight, bool showArrow); void setRoute(Route route); - void setPivot(Pivot pivot); void setOnFront(bool front); void setOnBack(bool back); @@ -57,7 +50,6 @@ protected: bool m_earlyBend; Route m_route; - Pivot m_pivot; private: Vec4i m_ownerRect; diff --git a/src/lib_gui/qt/view/QtGraphView.cpp b/src/lib_gui/qt/view/QtGraphView.cpp index 74a2d1a0..2e4f2c03 100644 --- a/src/lib_gui/qt/view/QtGraphView.cpp +++ b/src/lib_gui/qt/view/QtGraphView.cpp @@ -987,6 +987,7 @@ QtGraphNode* QtGraphView::createNodeRecursive( newNode->setPosition(node->position); newNode->setSize(node->size); + newNode->setColumnSize(node->columnSize); newNode->setIsActive(node->active); newNode->setMultipleActive(multipleActive); diff --git a/src/lib_gui/qt/view/graphElements/QtGraphEdge.cpp b/src/lib_gui/qt/view/graphElements/QtGraphEdge.cpp index 1821d7b4..8e1a7544 100644 --- a/src/lib_gui/qt/view/graphElements/QtGraphEdge.cpp +++ b/src/lib_gui/qt/view/graphElements/QtGraphEdge.cpp @@ -133,7 +133,6 @@ void QtGraphEdge::updateLine() QtLineItemBezier* bezier = new QtLineItemBezier(this); bezier->updateLine(ownerRect, rect, ownerParentRect, rect, style, m_weight, false); bezier->setRoute(route); - bezier->setPivot(QtLineItemBase::PIVOT_MIDDLE); QtLineItemStraight* line = new QtLineItemStraight(this); if (route == QtLineItemBase::ROUTE_HORIZONTAL) @@ -154,7 +153,6 @@ void QtGraphEdge::updateLine() QtLineItemBezier* bezier = new QtLineItemBezier(this); bezier->updateLine(ownerRect, targetRect, ownerParentRect, targetParentRect, style, m_weight, showArrow); bezier->setRoute(route); - bezier->setPivot(QtLineItemBase::PIVOT_MIDDLE); if (ownerNonGroupParent == targetNonGroupParent) { @@ -170,6 +168,11 @@ void QtGraphEdge::updateLine() } else { + const Vec2i& ownerColumnSize = m_owner->getLastParent()->getColumnSize(); + const Vec2i& targetColumnSize = m_target->getLastParent()->getColumnSize(); + ownerParentRect.z = std::max(ownerParentRect.x + ownerColumnSize.x, ownerParentRect.z()); + targetParentRect.z = std::max(targetParentRect.x + targetColumnSize.x, targetParentRect.z()); + if (!m_child) { m_child = new QtLineItemAngled(this); @@ -214,11 +217,6 @@ void QtGraphEdge::updateLine() child->setRoute(QtLineItemBase::ROUTE_HORIZONTAL); } - if (type == Edge::EDGE_AGGREGATION || type == Edge::EDGE_INHERITANCE) - { - child->setPivot(QtLineItemBase::PIVOT_MIDDLE); - } - bool showArrow = true; if (type == Edge::EDGE_AGGREGATION) { diff --git a/src/lib_gui/qt/view/graphElements/QtGraphNode.cpp b/src/lib_gui/qt/view/graphElements/QtGraphNode.cpp index 8c6d5faa..6259cd9c 100644 --- a/src/lib_gui/qt/view/graphElements/QtGraphNode.cpp +++ b/src/lib_gui/qt/view/graphElements/QtGraphNode.cpp @@ -109,6 +109,7 @@ bool QtGraphNode::setPosition(const Vec2i& position) if (offset.x != 0 || offset.y != 0) { this->moveBy(offset.x, offset.y); + setColumnSize(Vec2i()); notifyEdgesAfterMove(); return true; } @@ -116,7 +117,7 @@ bool QtGraphNode::setPosition(const Vec2i& position) return false; } -Vec2i QtGraphNode::getSize() const +const Vec2i& QtGraphNode::getSize() const { return m_size; } @@ -130,6 +131,16 @@ void QtGraphNode::setSize(const Vec2i& size) m_undefinedRect->setRect(1, 1, size.x - 2, size.y - 2); } +const Vec2i& QtGraphNode::getColumnSize() const +{ + return m_columnSize; +} + +void QtGraphNode::setColumnSize(const Vec2i& size) +{ + m_columnSize = size; +} + QSize QtGraphNode::size() const { return QSize(m_size.x, m_size.y); diff --git a/src/lib_gui/qt/view/graphElements/QtGraphNode.h b/src/lib_gui/qt/view/graphElements/QtGraphNode.h index 161a68cb..9ad7b152 100644 --- a/src/lib_gui/qt/view/graphElements/QtGraphNode.h +++ b/src/lib_gui/qt/view/graphElements/QtGraphNode.h @@ -46,9 +46,12 @@ public: Vec2i getPosition() const; virtual bool setPosition(const Vec2i& position); - Vec2i getSize() const; + const Vec2i& getSize() const; void setSize(const Vec2i& size); + const Vec2i& getColumnSize() const; + void setColumnSize(const Vec2i& size); + QSize size() const; void setSize(const QSize& size); @@ -124,6 +127,7 @@ protected: QGraphicsPixmapItem* m_icon = nullptr; Vec2i m_size; + Vec2i m_columnSize; bool m_isActive = false; bool m_multipleActive = false;