diff --git a/src/app/qt/graphics/QtGraphicsRoundedRectItem.cpp b/src/app/qt/graphics/QtGraphicsRoundedRectItem.cpp index d093e501..40abbbe4 100644 --- a/src/app/qt/graphics/QtGraphicsRoundedRectItem.cpp +++ b/src/app/qt/graphics/QtGraphicsRoundedRectItem.cpp @@ -33,6 +33,14 @@ void QtGraphicsRoundedRectItem::setShadow(QColor color, int blurRadius) this->setGraphicsEffect(effect); } +void QtGraphicsRoundedRectItem::setShadowEnabled(bool enabled) +{ + if (this->graphicsEffect()) + { + this->graphicsEffect()->setEnabled(enabled); + } +} + void QtGraphicsRoundedRectItem::setRadius(qreal radius) { m_radius = radius; diff --git a/src/app/qt/graphics/QtGraphicsRoundedRectItem.h b/src/app/qt/graphics/QtGraphicsRoundedRectItem.h index 764274b1..2526c20f 100644 --- a/src/app/qt/graphics/QtGraphicsRoundedRectItem.h +++ b/src/app/qt/graphics/QtGraphicsRoundedRectItem.h @@ -13,6 +13,7 @@ public: virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem* options, QWidget* widget); void setShadow(QColor color, int blurRadius); + void setShadowEnabled(bool enabled); void setRadius(qreal radius); private: diff --git a/src/app/qt/utility/QtGraphPostprocessor.cpp b/src/app/qt/utility/QtGraphPostprocessor.cpp index 9228b786..6aa7a7fa 100644 --- a/src/app/qt/utility/QtGraphPostprocessor.cpp +++ b/src/app/qt/utility/QtGraphPostprocessor.cpp @@ -66,8 +66,11 @@ void QtGraphPostprocessor::doPostprocessing(std::listgetPosition(); + node->setPosition(alignOnRaster(node->getPosition())); +} +Vec2i QtGraphPostprocessor::alignOnRaster(Vec2i position) +{ int rasterPosDivisor = s_cellSize + s_cellPadding; if(position.x % rasterPosDivisor != 0) @@ -110,7 +113,7 @@ void QtGraphPostprocessor::allignNodeOnRaster(QtGraphNode* node) position.y = t * rasterPosDivisor; } - node->setPosition(position); + return position; } void QtGraphPostprocessor::resolveOutliers(std::list>& nodes, const Vec2i& centerPoint) diff --git a/src/app/qt/utility/QtGraphPostprocessor.h b/src/app/qt/utility/QtGraphPostprocessor.h index 583d9a3a..cc68660d 100644 --- a/src/app/qt/utility/QtGraphPostprocessor.h +++ b/src/app/qt/utility/QtGraphPostprocessor.h @@ -14,6 +14,7 @@ public: static void doPostprocessing(std::list>& nodes); static void allignNodeOnRaster(QtGraphNode* node); + static Vec2i alignOnRaster(Vec2i position); private: static unsigned int s_cellSize; diff --git a/src/app/qt/view/QtGraphView.cpp b/src/app/qt/view/QtGraphView.cpp index 81d4b207..d4bdf9c7 100644 --- a/src/app/qt/view/QtGraphView.cpp +++ b/src/app/qt/view/QtGraphView.cpp @@ -4,6 +4,9 @@ #include #include #include +#include +#include +#include #include "qt/utility/QtGraphPostprocessor.h" #include "qt/utility/utilityQt.h" @@ -69,12 +72,7 @@ void QtGraphView::clear() GraphView::Metrics QtGraphView::getViewMetrics() const { GraphView::Metrics metrics; - QGraphicsView* view = getView(); - if (view == NULL) - { - return metrics; - } metrics.width = view->width(); metrics.height = view->height(); @@ -86,61 +84,35 @@ GraphView::Metrics QtGraphView::getViewMetrics() const return metrics; } -QGraphicsView* QtGraphView::getView() const +void QtGraphView::finishedTransition() { - QWidget* widget = QtViewWidgetWrapper::getWidgetOfView(this); + for (const std::shared_ptr& node : m_nodes) + { + node->setShadowEnabledRecursive(true); + } - return widget->findChild(""); + QGraphicsView* view = getView(); + view->setInteractive(true); + + switchToNewGraphData(); } -void QtGraphView::doRebuildGraph( - std::shared_ptr graph, - const std::vector& nodes, - const std::vector& edges -){ +void QtGraphView::switchToNewGraphData() +{ + m_oldGraph = m_graph; + + m_oldNodes = m_nodes; + m_oldEdges = m_edges; + + m_nodes.clear(); + m_edges.clear(); + QGraphicsView* view = getView(); - if (view == NULL) - { - LOG_WARNING("Failed to get QGraphicsView"); - return; - } + int margin = 25; + view->setSceneRect(itemsBoundingRect(m_oldNodes).adjusted(-margin, -margin, margin, margin).translated(m_sceneRectOffset)); - // Temporary stores all nodes (existing and newly created) needed in the new graph - // this is a relatively easy and cheap way to save existing nodes that are still needed - std::list> newNodes; - - // create nodes (or find existing nodes for re-use) - for (unsigned int i = 0; i < nodes.size(); i++) - { - std::shared_ptr node = createNodeRecursive(view, NULL, nodes[i]); - if (node) - { - newNodes.push_back(node); - } - } - - doClear(); - m_nodes = newNodes; - - for (unsigned int i = 0; i < edges.size(); i++) - { - std::shared_ptr edge = createEdge(view, edges[i]); - if (edge != NULL) - { - m_edges.push_back(edge); - } - } - - if (graph) - { - m_graph = graph; - } - - QtGraphPostprocessor::doPostprocessing(m_nodes); - - // Manually hover the items below the mouse cursor. - view->scene()->setSceneRect(view->scene()->itemsBoundingRect()); + // Manually hover the item below the mouse cursor. QPointF point = view->mapToScene(view->mapFromGlobal(QCursor::pos())); QGraphicsItem* item = view->scene()->itemAt(point, QTransform()); if (item) @@ -153,10 +125,77 @@ void QtGraphView::doRebuildGraph( } } +QGraphicsView* QtGraphView::getView() const +{ + QWidget* widget = QtViewWidgetWrapper::getWidgetOfView(this); + + QGraphicsView* view = widget->findChild(""); + + if (!view) + { + LOG_ERROR("Failed to get QGraphicsView"); + } + + return view; +} + +void QtGraphView::doRebuildGraph( + std::shared_ptr graph, + const std::vector& nodes, + const std::vector& edges +){ + QGraphicsView* view = getView(); + + m_nodes.clear(); + for (unsigned int i = 0; i < nodes.size(); i++) + { + std::shared_ptr node = createNodeRecursive(view, NULL, nodes[i]); + if (node) + { + m_nodes.push_back(node); + } + } + + QtGraphPostprocessor::doPostprocessing(m_nodes); + + QPointF center = itemsBoundingRect(m_nodes).center(); + Vec2i o = QtGraphPostprocessor::alignOnRaster(Vec2i(center.x(), center.y())); + QPointF offset = QPointF(o.x, o.y); + m_sceneRectOffset = offset - center; + + for (const std::shared_ptr& node : m_nodes) + { + node->setPos(node->pos() - offset); + } + + m_edges.clear(); + for (unsigned int i = 0; i < edges.size(); i++) + { + std::shared_ptr edge = createEdge(view, edges[i]); + if (edge) + { + m_edges.push_back(edge); + } + } + + if (graph) + { + m_graph = graph; + } + + createTransition(); +} + void QtGraphView::doClear() { m_nodes.clear(); m_edges.clear(); + + m_oldNodes.clear(); + m_oldEdges.clear(); + + m_graph.reset(); + m_oldGraph.reset(); } std::shared_ptr QtGraphView::findNodeRecursive(const std::list>& nodes, Id tokenId) @@ -258,3 +297,184 @@ std::shared_ptr QtGraphView::createEdge(QGraphicsView* view, const return NULL; } } + +template +QRectF QtGraphView::itemsBoundingRect(const std::list>& items) const +{ + QRectF boundingRect; + for (const std::shared_ptr& item : items) + { + boundingRect |= item->sceneBoundingRect(); + } + return boundingRect; +} + +void QtGraphView::compareNodesRecursive( + std::list> newSubNodes, + std::list> oldSubNodes, + std::list* appearingNodes, + std::list* vanishingNodes, + std::vector>* remainingNodes +){ + for (std::list>::iterator it = newSubNodes.begin(); it != newSubNodes.end(); it++) + { + bool remains = false; + + for (std::list>::iterator it2 = oldSubNodes.begin(); it2 != oldSubNodes.end(); it2++) + { + if (((*it)->getTokenId() && (*it)->getTokenId() == (*it2)->getTokenId()) || + ((*it)->isAccessNode() && (*it2)->isAccessNode() && + dynamic_cast((*it).get())->getAccessType() == + dynamic_cast((*it2).get())->getAccessType())) + { + remainingNodes->push_back(std::pair((*it).get(), (*it2).get())); + compareNodesRecursive((*it)->getSubNodes(), (*it2)->getSubNodes(), appearingNodes, vanishingNodes, remainingNodes); + + oldSubNodes.erase(it2); + remains = true; + break; + } + } + + if (!remains) + { + appearingNodes->push_back((*it).get()); + } + } + + for (std::shared_ptr& node : oldSubNodes) + { + vanishingNodes->push_back(node.get()); + } +} + +void QtGraphView::createTransition() +{ + std::list appearingNodes; + std::list vanishingNodes; + std::vector> remainingNodes; + + compareNodesRecursive(m_nodes, m_oldNodes, &appearingNodes, &vanishingNodes, &remainingNodes); + + if (!vanishingNodes.size() && !appearingNodes.size()) + { + switchToNewGraphData(); + return; + } + + for (const std::shared_ptr& node : m_nodes) + { + node->setShadowEnabledRecursive(false); + } + + for (const std::shared_ptr& node : m_oldNodes) + { + node->setShadowEnabledRecursive(false); + } + + QGraphicsView* view = getView(); + view->setInteractive(false); + + QSequentialAnimationGroup* group = new QSequentialAnimationGroup(); + + // fade out + if (vanishingNodes.size() || m_oldEdges.size()) + { + QParallelAnimationGroup* vanish = new QParallelAnimationGroup(); + + for (QtGraphNode* node : vanishingNodes) + { + QPropertyAnimation* anim = new QPropertyAnimation(node, "opacity"); + anim->setDuration(300); + anim->setStartValue(1.0f); + anim->setEndValue(0.0f); + + vanish->addAnimation(anim); + } + + for (std::shared_ptr edge : m_oldEdges) + { + QPropertyAnimation* anim = new QPropertyAnimation(edge.get(), "opacity"); + anim->setDuration(150); + anim->setStartValue(1.0f); + anim->setEndValue(0.0f); + + vanish->addAnimation(anim); + } + + group->addAnimation(vanish); + } + + // move and scale + if (remainingNodes.size()) + { + QParallelAnimationGroup* remain = new QParallelAnimationGroup(); + + for (std::pair p : remainingNodes) + { + QtGraphNode* newNode = p.first; + QtGraphNode* oldNode = p.second; + + QPropertyAnimation* anim = new QPropertyAnimation(oldNode, "pos"); + anim->setDuration(300); + anim->setStartValue(oldNode->pos()); + anim->setEndValue(newNode->pos()); + + remain->addAnimation(anim); + + connect(anim, SIGNAL(finished()), newNode, SLOT(showNode())); + connect(anim, SIGNAL(finished()), oldNode, SLOT(hideNode())); + newNode->hide(); + + anim = new QPropertyAnimation(oldNode, "size"); + anim->setDuration(300); + anim->setStartValue(oldNode->size()); + anim->setEndValue(newNode->size()); + + remain->addAnimation(anim); + + if (newNode->isAccessNode() && newNode->getSubNodes().size() == 0 && oldNode->getSubNodes().size() > 0) + { + dynamic_cast(oldNode)->hideLabel(); + } + } + + group->addAnimation(remain); + } + + // fade in + if (appearingNodes.size() || m_edges.size()) + { + QParallelAnimationGroup* appear = new QParallelAnimationGroup(); + + for (QtGraphNode* node : appearingNodes) + { + QPropertyAnimation* anim = new QPropertyAnimation(node, "opacity"); + anim->setDuration(300); + anim->setStartValue(0.0f); + anim->setEndValue(1.0f); + + appear->addAnimation(anim); + + connect(anim, SIGNAL(finished()), node, SLOT(blendIn())); + node->blendOut(); + } + + for (std::shared_ptr edge : m_edges) + { + QPropertyAnimation* anim = new QPropertyAnimation(edge.get(), "opacity"); + anim->setDuration(150); + anim->setStartValue(0.0f); + anim->setEndValue(1.0f); + + appear->addAnimation(anim); + + edge->setOpacity(0.0f); + } + + group->addAnimation(appear); + } + + connect(group, SIGNAL(finished()), this, SLOT(finishedTransition())); + group->start(); +} diff --git a/src/app/qt/view/QtGraphView.h b/src/app/qt/view/QtGraphView.h index 6e76d48c..1cf9ce41 100644 --- a/src/app/qt/view/QtGraphView.h +++ b/src/app/qt/view/QtGraphView.h @@ -1,6 +1,8 @@ #ifndef QT_GRAPH_VIEW_H #define QT_GRAPH_VIEW_H +#include + #include "qt/utility/QtThreadedFunctor.h" #include "utility/math/MatrixDynamicBase.h" #include "utility/math/Vector4.h" @@ -14,8 +16,12 @@ class QGraphicsView; class QtGraphEdge; class QtGraphNode; -class QtGraphView: public GraphView +class QtGraphView + : public QObject + , public GraphView { + Q_OBJECT + public: QtGraphView(ViewLayout* viewLayout); virtual ~QtGraphView(); @@ -30,7 +36,12 @@ public: virtual GraphView::Metrics getViewMetrics() const; +private slots: + void finishedTransition(); + private: + void switchToNewGraphData(); + QGraphicsView* getView() const; void doRebuildGraph(std::shared_ptr graph, const std::vector& nodes, const std::vector& edges); @@ -42,13 +53,31 @@ private: QGraphicsView* view, std::shared_ptr parentNode, const DummyNode& node); std::shared_ptr createEdge(QGraphicsView* view, const DummyEdge& edge); + template + QRectF itemsBoundingRect(const std::list>& items) const; + + void compareNodesRecursive( + std::list> newSubNodes, + std::list> oldSubNodes, + std::list* appearingNodes, + std::list* vanishingNodes, + std::vector>* remainingNodes); + + void createTransition(); + QtThreadedFunctor, const std::vector&, const std::vector&> m_rebuildGraphFunctor; QtThreadedFunctor m_clearFunctor; std::shared_ptr m_graph; + std::shared_ptr m_oldGraph; std::list> m_edges; + std::list> m_oldEdges; + std::list> m_nodes; + std::list> m_oldNodes; + + QPointF m_sceneRectOffset; }; #endif // QT_GRAPH_VIEW_H diff --git a/src/app/qt/view/graphElements/QtGraphEdge.h b/src/app/qt/view/graphElements/QtGraphEdge.h index 5abea7da..a6b26b0e 100644 --- a/src/app/qt/view/graphElements/QtGraphEdge.h +++ b/src/app/qt/view/graphElements/QtGraphEdge.h @@ -55,9 +55,13 @@ private: class QtGraphEdge - : public GraphEdge + : public QObject + , public GraphEdge , public QGraphicsItemGroup { + Q_OBJECT + Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity) + public: QtGraphEdge(const std::weak_ptr& owner, const std::weak_ptr& target, const Edge* data); virtual ~QtGraphEdge(); diff --git a/src/app/qt/view/graphElements/QtGraphNode.cpp b/src/app/qt/view/graphElements/QtGraphNode.cpp index e3ea95de..08592bdd 100644 --- a/src/app/qt/view/graphElements/QtGraphNode.cpp +++ b/src/app/qt/view/graphElements/QtGraphNode.cpp @@ -9,10 +9,29 @@ #include "qt/graphics/QtGraphicsRoundedRectItem.h" #include "qt/utility/QtDeviceScaledPixmap.h" -#include "qt/utility/QtGraphPostprocessor.h" #include "qt/view/graphElements/nodeComponents/QtGraphNodeComponent.h" #include "qt/view/graphElements/QtGraphEdge.h" +void QtGraphNode::blendIn() +{ + setOpacity(1.0f); +} + +void QtGraphNode::blendOut() +{ + setOpacity(0.0f); +} + +void QtGraphNode::showNode() +{ + this->show(); +} + +void QtGraphNode::hideNode() +{ + this->hide(); +} + QFont QtGraphNode::getFontForNodeType(Node::NodeType type) { QFont font("Source Code Pro"); @@ -92,6 +111,11 @@ void QtGraphNode::setName(const std::string& name) m_text->setText(QString::fromStdString(name)); } +bool QtGraphNode::isAccessNode() const +{ + return false; +} + Vec2i QtGraphNode::getPosition() const { return Vec2i(this->scenePos().x(), this->scenePos().y()); @@ -205,6 +229,16 @@ unsigned int QtGraphNode::getInEdgeCount() const return m_inEdges.size(); } +QSize QtGraphNode::size() const +{ + return QSize(m_size.x, m_size.y); +} + +void QtGraphNode::setSize(const QSize& size) +{ + setSize(Vec2i(size.width(), size.height())); +} + bool QtGraphNode::getIsActive() const { return m_isActive; @@ -394,6 +428,16 @@ void QtGraphNode::setStyle() m_text->setPos(padding.x, padding.y); } +void QtGraphNode::setShadowEnabledRecursive(bool enabled) +{ + m_rect->setShadowEnabled(enabled); + + for (const std::shared_ptr& node : m_subNodes) + { + node->setShadowEnabledRecursive(enabled); + } +} + void QtGraphNode::onClick() { if (m_data && !m_data->isType(Node::NODE_UNDEFINED | Node::NODE_NAMESPACE)) @@ -468,8 +512,6 @@ void QtGraphNode::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) parent->mouseReleaseEvent(event); } } - - QtGraphPostprocessor::allignNodeOnRaster(this); } void QtGraphNode::hoverEnterEvent(QGraphicsSceneHoverEvent* event) diff --git a/src/app/qt/view/graphElements/QtGraphNode.h b/src/app/qt/view/graphElements/QtGraphNode.h index 864942c5..05346fe6 100644 --- a/src/app/qt/view/graphElements/QtGraphNode.h +++ b/src/app/qt/view/graphElements/QtGraphNode.h @@ -11,9 +11,22 @@ class QtGraphicsRoundedRectItem; class QtGraphNodeComponent; class QtGraphNode - : public GraphNode + : public QObject + , public GraphNode , public QGraphicsRectItem { + Q_OBJECT + Q_PROPERTY(QPointF pos READ pos WRITE setPos) + Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity) + Q_PROPERTY(QSize size READ size WRITE setSize) + +public slots: + void blendIn(); + void blendOut(); + + void showNode(); + void hideNode(); + public: static QFont getFontForNodeType(Node::NodeType type); @@ -24,6 +37,8 @@ public: virtual std::string getName() const; void setName(const std::string& name); + virtual bool isAccessNode() const; + virtual Vec2i getPosition() const; virtual bool setPosition(const Vec2i& position); virtual void moveTo(const Vec2i& position); @@ -40,6 +55,9 @@ public: virtual unsigned int getOutEdgeCount() const; virtual unsigned int getInEdgeCount() const; + QSize size() const; + void setSize(const QSize& size); + bool getIsActive() const; void setIsActive(bool isActive); @@ -52,6 +70,7 @@ public: virtual void addSubNode(const std::shared_ptr& node); void setStyle(); + void setShadowEnabledRecursive(bool enabled); virtual void onClick(); void hoverEnter(); diff --git a/src/app/qt/view/graphElements/QtGraphNodeAccess.cpp b/src/app/qt/view/graphElements/QtGraphNodeAccess.cpp index 705d6bc0..06a64546 100644 --- a/src/app/qt/view/graphElements/QtGraphNodeAccess.cpp +++ b/src/app/qt/view/graphElements/QtGraphNodeAccess.cpp @@ -95,6 +95,16 @@ QtGraphNodeAccess::~QtGraphNodeAccess() { } +bool QtGraphNodeAccess::isAccessNode() const +{ + return true; +} + +TokenComponentAccess::AccessType QtGraphNodeAccess::getAccessType() const +{ + return m_access; +} + void QtGraphNodeAccess::setSize(const Vec2i& size) { QtGraphNode::setSize(size); @@ -105,7 +115,6 @@ void QtGraphNodeAccess::setSize(const Vec2i& size) void QtGraphNodeAccess::addSubNode(const std::shared_ptr& node) { QtGraphNode::addSubNode(node); - m_text->show(); } @@ -118,3 +127,8 @@ void QtGraphNodeAccess::onClick() MessageGraphNodeExpand(parent->getData()->getId(), m_access).dispatch(); } } + +void QtGraphNodeAccess::hideLabel() +{ + m_text->hide(); +} diff --git a/src/app/qt/view/graphElements/QtGraphNodeAccess.h b/src/app/qt/view/graphElements/QtGraphNodeAccess.h index 5acf5dd5..6b4e0da5 100644 --- a/src/app/qt/view/graphElements/QtGraphNodeAccess.h +++ b/src/app/qt/view/graphElements/QtGraphNodeAccess.h @@ -20,16 +20,18 @@ public: QGraphicsSimpleTextItem* m_number; }; - QtGraphNodeAccess(TokenComponentAccess::AccessType accessType, bool expanded, int invisibleSubNodeCount); virtual ~QtGraphNodeAccess(); + virtual bool isAccessNode() const; + TokenComponentAccess::AccessType getAccessType() const; + virtual void setSize(const Vec2i& size); - virtual void addSubNode(const std::shared_ptr& node); - virtual void onClick(); + void hideLabel(); + private: TokenComponentAccess::AccessType m_access; diff --git a/src/app/qt/view/graphElements/nodeComponents/QtGraphNodeComponentMoveable.cpp b/src/app/qt/view/graphElements/nodeComponents/QtGraphNodeComponentMoveable.cpp index c68d8183..f3109da4 100644 --- a/src/app/qt/view/graphElements/nodeComponents/QtGraphNodeComponentMoveable.cpp +++ b/src/app/qt/view/graphElements/nodeComponents/QtGraphNodeComponentMoveable.cpp @@ -2,6 +2,7 @@ #include +#include "qt/utility/QtGraphPostprocessor.h" #include "qt/view/graphElements/QtGraphNode.h" QtGraphNodeComponentMoveable::QtGraphNodeComponentMoveable(const std::weak_ptr& graphNode) @@ -35,3 +36,17 @@ void QtGraphNodeComponentMoveable::nodeMouseMoveEvent(QGraphicsSceneMouseEvent* event->accept(); } } + +void QtGraphNodeComponentMoveable::nodeMouseReleaseEvent(QGraphicsSceneMouseEvent* event) +{ + if (event->isAccepted()) + { + return; + } + + std::shared_ptr node = m_graphNode.lock(); + if (node != NULL) + { + QtGraphPostprocessor::allignNodeOnRaster(node.get()); + } +} diff --git a/src/app/qt/view/graphElements/nodeComponents/QtGraphNodeComponentMoveable.h b/src/app/qt/view/graphElements/nodeComponents/QtGraphNodeComponentMoveable.h index 4ba698c5..67d10322 100644 --- a/src/app/qt/view/graphElements/nodeComponents/QtGraphNodeComponentMoveable.h +++ b/src/app/qt/view/graphElements/nodeComponents/QtGraphNodeComponentMoveable.h @@ -14,6 +14,7 @@ public: virtual void nodeMousePressEvent(QGraphicsSceneMouseEvent* event); virtual void nodeMouseMoveEvent(QGraphicsSceneMouseEvent* event); + virtual void nodeMouseReleaseEvent(QGraphicsSceneMouseEvent* event); private: Vec2i m_mouseOffset; diff --git a/src/lib/component/view/graphElements/GraphNode.h b/src/lib/component/view/graphElements/GraphNode.h index 1fa9d66e..8fac1504 100644 --- a/src/lib/component/view/graphElements/GraphNode.h +++ b/src/lib/component/view/graphElements/GraphNode.h @@ -25,6 +25,8 @@ public: const Node* getData() const; void setData(const Node* data); + virtual bool isAccessNode() const = 0; + virtual Vec2i getPosition() const = 0; virtual bool setPosition(const Vec2i& position) = 0; virtual void moveTo(const Vec2i& position) = 0;