subsurface/qt-quick/chartitem.cpp
Berthold Stoeger ebf9ce6d86 profile: port picture code to qt-quick
This was very painful, because I had to implement rearranging the
paint order of the QSGNodes. The resulting code appears quite
brittle. Let's see where that brings us.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2024-09-12 15:15:32 +02:00

321 lines
7.6 KiB
C++

// SPDX-License-Identifier: GPL-2.0
#include "chartitem.h"
#include "chartitemhelper.h"
#include "chartitem_private.h"
#include <cmath>
#include <QGraphicsScene>
#include <QQuickWindow>
#include <QSGFlatColorMaterial>
#include <QSGImageNode>
ChartItem::ChartItem(ChartView &v, size_t z, bool dragable) :
dirty(false), prev(nullptr), next(nullptr),
zValue(z), dragable(dragable), view(v)
{
// Register before the derived constructors run, so that the
// derived classes can mark the item as dirty in the constructor.
v.registerChartItem(*this);
}
ChartItem::~ChartItem()
{
}
QSizeF ChartItem::sceneSize() const
{
return view.size();
}
void ChartItem::markDirty()
{
view.registerDirtyChartItem(*this);
}
QRectF ChartItem::getRect() const
{
return rect;
}
void ChartItem::setPos(QPointF)
{
}
void ChartItem::stopDrag(QPointF pos)
{
}
static int round_up(double f)
{
return static_cast<int>(ceil(f));
}
ChartPixmapItem::ChartPixmapItem(ChartView &v, size_t z, bool dragable) : HideableChartItem(v, z, dragable),
scale(1.0), positionDirty(false), textureDirty(false)
{
}
ChartPixmapItem::~ChartPixmapItem()
{
painter.reset(); // Make sure to destroy painter before image that is painted on
}
void ChartPixmapItem::setTextureDirty()
{
textureDirty = true;
markDirty();
}
void ChartPixmapItem::setPositionDirty()
{
positionDirty = true;
markDirty();
}
void ChartPixmapItem::render()
{
doRearrange();
if (!node) {
createNode(view.w()->createImageNode());
addNodeToView();
}
updateVisible();
if (!img) {
resize(QSizeF(1,1));
img->fill(Qt::transparent);
}
if (textureDirty) {
texture.reset(view.w()->createTextureFromImage(*img, QQuickWindow::TextureHasAlphaChannel));
node->node->setTexture(texture.get());
textureDirty = false;
}
if (positionDirty) {
node->node->setRect(rect);
positionDirty = false;
}
}
// Scale size and round to integer (because non-integers give strange artifacts for me).
static QSizeF scaleSize(const QSizeF &s, double scale)
{
return { round(s.width() * scale),
round(s.height() * scale) };
}
void ChartPixmapItem::resize(QSizeF size)
{
QSize s_int(round_up(size.width()), round_up(size.height()));
if (img && s_int == img->size())
return;
painter.reset();
img.reset(new QImage(s_int, QImage::Format_ARGB32));
if (!img->isNull()) {
painter.reset(new QPainter(img.get()));
painter->setRenderHint(QPainter::Antialiasing);
}
rect.setSize(scaleSize(size, scale));
setPositionDirty(); // position includes the size.
setTextureDirty();
}
void ChartPixmapItem::setPos(QPointF pos)
{
rect.moveTopLeft(pos);
setPositionDirty();
}
void ChartPixmapItem::setScale(double scaleIn)
{
scale = scaleIn;
if (img) {
rect.setSize(scaleSize(img->size(), scale));
setPositionDirty(); // position includes the size.
}
}
void ChartGraphicsSceneItem::draw(QSizeF s, QColor background, QGraphicsScene &scene)
{
resize(s); // Noop if size doesn't change
if (!painter)
return; // Happens if we resize to (0,0)
img->fill(background);
scene.render(painter.get(), QRect(QPoint(), img->size()), scene.sceneRect(), Qt::IgnoreAspectRatio);
setTextureDirty();
}
ChartRectItem::ChartRectItem(ChartView &v, size_t z, const QPen &pen,
const QBrush &brush, double radius, bool dragable) :
ChartPixmapItem(v, z, dragable),
pen(pen), brush(brush), radius(radius)
{
}
ChartRectItem::~ChartRectItem()
{
}
void ChartRectItem::resize(QSizeF size)
{
ChartPixmapItem::resize(size);
img->fill(Qt::transparent);
painter->setPen(pen);
painter->setBrush(brush);
QSize imgSize = img->size();
int width = pen.width();
QRect rect(width / 2, width / 2, imgSize.width() - width, imgSize.height() - width);
painter->drawRoundedRect(rect, radius, radius, Qt::AbsoluteSize);
}
AnimatedChartRectItem::~AnimatedChartRectItem()
{
}
void AnimatedChartRectItem::setPixmap(const QPixmap &p, int animSpeed)
{
if (animSpeed <= 0) {
resize(p.size());
painter->drawPixmap(0, 0, p, 0, 0, p.width(), p.height());
setTextureDirty();
return;
}
pixmap = p;
originalSize = img ? img->size() : QSize(1,1);
}
static int mid(int from, int to, double fraction)
{
return fraction == 1.0 ? to
: lrint(from + (to - from) * fraction);
}
void AnimatedChartRectItem::anim(double fraction)
{
QSize s(mid(originalSize.width(), pixmap.width(), fraction),
mid(originalSize.height(), pixmap.height(), fraction));
resize(s);
painter->drawPixmap(0, 0, pixmap, 0, 0, s.width(), s.height());
setTextureDirty();
}
ChartTextItem::ChartTextItem(ChartView &v, size_t z, const QFont &f, const std::vector<QString> &text, bool center) :
ChartPixmapItem(v, z), f(f), center(center)
{
QFontMetrics fm(f);
double totalWidth = 1.0;
fontHeight = static_cast<double>(fm.height());
double totalHeight = std::max(1.0, static_cast<double>(text.size()) * fontHeight);
items.reserve(text.size());
for (const QString &s: text) {
double w = fm.size(Qt::TextSingleLine, s).width();
items.push_back({ s, w });
if (w > totalWidth)
totalWidth = w;
}
resize(QSizeF(totalWidth, totalHeight));
}
ChartTextItem::ChartTextItem(ChartView &v, size_t z, const QFont &f, const QString &text) :
ChartTextItem(v, z, f, std::vector<QString>({ text }), true)
{
}
void ChartTextItem::setColor(const QColor &c)
{
setColor(c, Qt::transparent);
}
void ChartTextItem::setColor(const QColor &c, const QColor &background)
{
img->fill(background);
double y = 0.0;
painter->setPen(QPen(c));
painter->setFont(f);
double totalWidth = getRect().width();
for (const auto &[s, w]: items) {
double x = center ? round((totalWidth - w) / 2.0) : 0.0;
QRectF rect(x, y, w, fontHeight);
painter->drawText(rect, s);
y += fontHeight;
}
setTextureDirty();
}
ChartLineItemBase::~ChartLineItemBase()
{
}
void ChartLineItemBase::setLine(QPointF from, QPointF to)
{
rect = QRectF(from, to);
positionDirty = true;
markDirty();
}
void ChartLineItem::render()
{
doRearrange();
if (!node) {
geometry.reset(new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 2));
geometry->setDrawingMode(QSGGeometry::DrawLines);
material.reset(new QSGFlatColorMaterial);
createNode();
node->setGeometry(geometry.get());
node->setMaterial(material.get());
addNodeToView();
positionDirty = materialDirty = true;
}
updateVisible();
if (positionDirty) {
// Attention: width is a geometry property and therefore handled by position dirty!
geometry->setLineWidth(static_cast<float>(width));
auto vertices = geometry->vertexDataAsPoint2D();
setPoint(vertices[0], rect.topLeft());
setPoint(vertices[1], rect.bottomRight());
node->markDirty(QSGNode::DirtyGeometry);
}
if (materialDirty) {
material->setColor(color);
node->markDirty(QSGNode::DirtyMaterial);
}
positionDirty = materialDirty = false;
}
void ChartRectLineItem::render()
{
doRearrange();
if (!node) {
geometry.reset(new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 5));
geometry->setDrawingMode(QSGGeometry::DrawLineStrip);
material.reset(new QSGFlatColorMaterial);
createNode();
node->setGeometry(geometry.get());
node->setMaterial(material.get());
addNodeToView();
positionDirty = materialDirty = true;
}
updateVisible();
if (positionDirty) {
// Attention: width is a geometry property and therefore handled by position dirty!
geometry->setLineWidth(static_cast<float>(width));
auto vertices = geometry->vertexDataAsPoint2D();
setPoint(vertices[0], rect.topLeft());
setPoint(vertices[1], rect.bottomLeft());
setPoint(vertices[2], rect.bottomRight());
setPoint(vertices[3], rect.topRight());
setPoint(vertices[4], rect.topLeft());
node->markDirty(QSGNode::DirtyGeometry);
}
if (materialDirty) {
material->setColor(color);
node->markDirty(QSGNode::DirtyMaterial);
}
positionDirty = materialDirty = false;
}