diff options
-rw-r--r-- | stats/barseries.cpp | 2 | ||||
-rw-r--r-- | stats/chartitem.cpp | 24 | ||||
-rw-r--r-- | stats/chartitem.h | 53 | ||||
-rw-r--r-- | stats/statshelper.h | 79 |
4 files changed, 138 insertions, 20 deletions
diff --git a/stats/barseries.cpp b/stats/barseries.cpp index d7211212e..7218c3255 100644 --- a/stats/barseries.cpp +++ b/stats/barseries.cpp @@ -97,7 +97,7 @@ BarSeries::BarLabel::BarLabel(StatsView &view, const std::vector<QString> &label void BarSeries::BarLabel::setVisible(bool visible) { - // item->setVisible(visible); TODO! + item->setVisible(visible); } void BarSeries::BarLabel::highlight(bool highlight, int bin_nr, int binCount) diff --git a/stats/chartitem.cpp b/stats/chartitem.cpp index bb01c6e2c..ce5e6860c 100644 --- a/stats/chartitem.cpp +++ b/stats/chartitem.cpp @@ -31,7 +31,7 @@ QSizeF ChartItem::sceneSize() const return view.size(); } -ChartPixmapItem::ChartPixmapItem(StatsView &v, ChartZValue z) : ChartItem(v, z), +ChartPixmapItem::ChartPixmapItem(StatsView &v, ChartZValue z) : HideableChartItem(v, z), positionDirty(false), textureDirty(false) { } @@ -56,7 +56,7 @@ void ChartPixmapItem::setPositionDirty() void ChartPixmapItem::render() { if (!node) { - node.reset(view.w()->createImageNode()); + createNode(view.w()->createImageNode()); view.addQSGNode(node.get(), zValue); } if (!img) { @@ -65,11 +65,11 @@ void ChartPixmapItem::render() } if (textureDirty) { texture.reset(view.w()->createTextureFromImage(*img, QQuickWindow::TextureHasAlphaChannel)); - node->setTexture(texture.get()); + node->node->setTexture(texture.get()); textureDirty = false; } if (positionDirty) { - node->setRect(rect); + node->node->setRect(rect); positionDirty = false; } } @@ -151,7 +151,7 @@ void ChartTextItem::setColor(const QColor &c) setTextureDirty(); } -ChartLineItem::ChartLineItem(StatsView &v, ChartZValue z, QColor color, double width) : ChartItem(v, z), +ChartLineItem::ChartLineItem(StatsView &v, ChartZValue z, QColor color, double width) : HideableChartItem(v, z), color(color), width(width), positionDirty(false), materialDirty(false) { } @@ -172,7 +172,7 @@ void ChartLineItem::render() geometry.reset(new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 2)); geometry->setDrawingMode(QSGGeometry::DrawLines); material.reset(new QSGFlatColorMaterial); - node.reset(new QSGGeometryNode); + createNode(); node->setGeometry(geometry.get()); node->setMaterial(material.get()); view.addQSGNode(node.get(), zValue); @@ -204,7 +204,7 @@ void ChartLineItem::setLine(QPointF fromIn, QPointF toIn) view.registerDirtyChartItem(*this); } -ChartBarItem::ChartBarItem(StatsView &v, ChartZValue z, double borderWidth, bool horizontal) : ChartItem(v, z), +ChartBarItem::ChartBarItem(StatsView &v, ChartZValue z, double borderWidth, bool horizontal) : HideableChartItem(v, z), borderWidth(borderWidth), horizontal(horizontal), positionDirty(false), colorDirty(false) { @@ -217,7 +217,7 @@ ChartBarItem::~ChartBarItem() void ChartBarItem::render() { if (!node) { - node.reset(view.w()->createRectangleNode()); + createNode(view.w()->createRectangleNode()); borderGeometry.reset(new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 4)); borderGeometry->setDrawingMode(QSGGeometry::DrawLineLoop); @@ -227,19 +227,20 @@ void ChartBarItem::render() borderNode->setGeometry(borderGeometry.get()); borderNode->setMaterial(borderMaterial.get()); - node->appendChildNode(borderNode.get()); + node->node->appendChildNode(borderNode.get()); view.addQSGNode(node.get(), zValue); positionDirty = colorDirty = true; } if (colorDirty) { - node->setColor(color); + node->node->setColor(color); borderMaterial->setColor(borderColor); + node->node->markDirty(QSGNode::DirtyMaterial); borderNode->markDirty(QSGNode::DirtyMaterial); } if (positionDirty) { - node->setRect(rect); + node->node->setRect(rect); auto vertices = borderGeometry->vertexDataAsPoint2D(); if (horizontal) { setPoint(vertices[0], rect.topLeft()); @@ -252,6 +253,7 @@ void ChartBarItem::render() setPoint(vertices[2], rect.topRight()); setPoint(vertices[3], rect.bottomRight()); } + node->node->markDirty(QSGNode::DirtyGeometry); borderNode->markDirty(QSGNode::DirtyGeometry); } diff --git a/stats/chartitem.h b/stats/chartitem.h index 93c80547f..a87d5fe10 100644 --- a/stats/chartitem.h +++ b/stats/chartitem.h @@ -4,6 +4,8 @@ #ifndef CHART_ITEM_H #define CHART_ITEM_H +#include "statshelper.h" + #include <memory> #include <QPainter> @@ -18,19 +20,35 @@ enum class ChartZValue : int; class ChartItem { public: - ChartItem(StatsView &v, ChartZValue z); - virtual ~ChartItem(); virtual void render() = 0; // Only call on render thread! bool dirty; // If true, call render() when rebuilding the scene ChartItem *dirtyPrev, *dirtyNext; // Double linked list of dirty items const ChartZValue zValue; protected: + ChartItem(StatsView &v, ChartZValue z); + virtual ~ChartItem(); QSizeF sceneSize() const; StatsView &view; }; +template <typename Node> +class HideableChartItem : public ChartItem { +protected: + HideableChartItem(StatsView &v, ChartZValue z); + std::unique_ptr<Node> node; + bool visible; // Argh. If visibility is set before node is created, we have to cache it. + template<class... Args> + void createNode(Args&&... args); // Call to create node with visibility flag. +public: + void setVisible(bool visible); +}; + +// A shortcut for ChartItems based on a hideable proxy item +template <typename Node> +using HideableChartProxyItem = HideableChartItem<HideableQSGNode<QSGProxyNode<Node>>>; + // A chart item that blits a precalculated pixmap onto the scene. -class ChartPixmapItem : public ChartItem { +class ChartPixmapItem : public HideableChartProxyItem<QSGImageNode> { public: ChartPixmapItem(StatsView &v, ChartZValue z); ~ChartPixmapItem(); @@ -48,7 +66,6 @@ private: QRectF rect; bool positionDirty; // true if the position changed since last render bool textureDirty; // true if the pixmap changed since last render - std::unique_ptr<QSGImageNode> node; std::unique_ptr<QSGTexture> texture; }; @@ -80,7 +97,7 @@ private: std::vector<Item> items; }; -class ChartLineItem : public ChartItem { +class ChartLineItem : public HideableChartItem<HideableQSGNode<QSGGeometryNode>> { public: ChartLineItem(StatsView &v, ChartZValue z, QColor color, double width); ~ChartLineItem(); @@ -93,13 +110,12 @@ private: bool horizontal; bool positionDirty; bool materialDirty; - std::unique_ptr<QSGGeometryNode> node; std::unique_ptr<QSGFlatColorMaterial> material; std::unique_ptr<QSGGeometry> geometry; }; // A bar in a bar chart: a rectangle bordered by lines. -class ChartBarItem : public ChartItem { +class ChartBarItem : public HideableChartProxyItem<QSGRectangleNode> { public: ChartBarItem(StatsView &v, ChartZValue z, double borderWidth, bool horizontal); ~ChartBarItem(); @@ -114,10 +130,31 @@ private: bool horizontal; bool positionDirty; bool colorDirty; - std::unique_ptr<QSGRectangleNode> node; std::unique_ptr<QSGGeometryNode> borderNode; std::unique_ptr<QSGFlatColorMaterial> borderMaterial; std::unique_ptr<QSGGeometry> borderGeometry; }; +// Implementation detail of templates - move to serparate header file +template <typename Node> +void HideableChartItem<Node>::setVisible(bool visibleIn) +{ + visible = visibleIn; + if (node) + node->setVisible(visible); +} + +template <typename Node> +template<class... Args> +void HideableChartItem<Node>::createNode(Args&&... args) +{ + node.reset(new Node(visible, std::forward<Args>(args)...)); +} + +template <typename Node> +HideableChartItem<Node>::HideableChartItem(StatsView &v, ChartZValue z) : ChartItem(v, z), + visible(true) +{ +} + #endif diff --git a/stats/statshelper.h b/stats/statshelper.h index 0ea39763d..29dc083a4 100644 --- a/stats/statshelper.h +++ b/stats/statshelper.h @@ -4,9 +4,11 @@ // is for historical reasons to ease transition from QtCharts // and might be removed. #ifndef STATSHELPER_H +#define STATSHELPER_H #include <memory> #include <QGraphicsScene> +#include <QSGNode> template <typename T, class... Args> T *createItem(QGraphicsScene *scene, Args&&... args) @@ -22,4 +24,81 @@ std::unique_ptr<T> createItemPtr(QGraphicsScene *scene, Args&&... args) return std::unique_ptr<T>(createItem<T>(scene, std::forward<Args>(args)...)); } +// In general, we want chart items to be hideable. For example to show/hide +// labels on demand. Very sadly, the QSG API is absolutely terrible with +// respect to temporarily disabling. Instead of simply having a flag, +// a QSGNode is queried using the "isSubtreeBlocked()" virtual function(!). +// +// Not only is this a slow operation performed on every single node, it +// also is often not possible to override this function: For improved +// performance, the documentation recommends to create QSG nodes via +// QQuickWindow. This provides nodes optimized for the actual hardware. +// However, this obviously means that these nodes cannot be derived from! +// +// In that case, there are two possibilities: Add a proxy node with an +// overridden "isSubtreeBlocked()" function or remove the node from the +// scene. The former was chosen here, because it is less complex. +// +// The following slightly cryptic templates are used to unify the two +// cases: The QSGNode is generated by our own code or the QSGNode is +// obtained from QQuickWindow. +// +// The "HideableQSGNode<Node>" template augments the QSGNode "Node" +// by a "setVisible()" function and overrides "isSubtreeBlocked()" +// +// The "QSGProxyNode<Node>" template is a QSGNode with a single +// child of type "Node". +// +// Thus, if the node can be created, use: +// HideableQSGNode<NodeTypeThatCanBeCreated> node +// and if the node can only be obtained from QQuickWindow, use: +// HideableQSGNode<QSGProxyNode<NodeThatCantBeCreated>> node +// The latter should obviously be typedef-ed. +// +// Yes, that's all horrible, but if nothing else it teaches us about +// composition. +template <typename Node> +class HideableQSGNode : public Node { + bool hidden; + bool isSubtreeBlocked() const override final; +public: + template<class... Args> + HideableQSGNode(bool visible, Args&&... args); + void setVisible(bool visible); +}; + +template <typename Node> +class QSGProxyNode : public QSGNode { +public: + std::unique_ptr<Node> node; + QSGProxyNode(Node *node); +}; + +// Implementation detail of templates - move to serparate header file +template <typename Node> +QSGProxyNode<Node>::QSGProxyNode(Node *node) : node(node) +{ + appendChildNode(node); +} + +template <typename Node> +bool HideableQSGNode<Node>::isSubtreeBlocked() const +{ + return hidden; +} + +template <typename Node> +template<class... Args> +HideableQSGNode<Node>::HideableQSGNode(bool visible, Args&&... args) : + Node(std::forward<Args>(args)...), + hidden(!visible) +{ +} + +template <typename Node> +void HideableQSGNode<Node>::setVisible(bool visible) +{ + hidden = !visible; +} + #endif |