QScrollArea in Qml: Flickable + QQuickPaintedItem

2019-05-26 13:03发布

I'm trying to realize something similiar to QScrollArea (in widgets world) with the help of Qml. I decided to probe Flickable plus QQuickPaintedItem based item (named Drawer in my case):

Flickable {
  ...
  onContentXChanged(): {
  drawer.update()
  }

Drawer {
  id: drawer
  ...
}

Drawer's render target is set to FrameBufferObject. Its paint function looks like this:

void Drawer::paint(QPainter *painter)
{
   // Some function to compute rect which is needed to be redrawn
   QRect updateRect = computeUpdateRect();

   // How to shift contents of Frame buffer, e.g. to right, and draw only updateRect in this space?
}

Imagine how we do scrolling in QScrollArea widget, e.g. to left: all entry of viewport is shifted to right and the only small rect in left is redrawn. I want to do the same with Flickable+QQuickPaintedItem. But I can't understand some things:

How can I manipulate Frame Buffer object inside QQuickPaintedItem? Maybe there is some more right way to implement QScrollArea in QML?

By the way, is double buffering enabled by default in QQuickPaintedItem?

For implementing with Flickable: To provide more info about task: I have a very big "picture". So I cannot load it whole into memory, but I have to navigate through it with something like viewport.

1条回答
Lonely孤独者°
2楼-- · 2019-05-26 13:48

A scroll area or a flickable are used when you want to encapsulate a larger content in a smaller area and navigate around it. And in your case that is... not the case. You are not practically using a scroll area as your image is never larger than the scroll area size, you just want to fake it, which is actually quite easy:

#include <QQuickPaintedItem>
#include <QImage>
#include <QPainter>

class PImage : public QQuickPaintedItem {
    Q_OBJECT
public:
    PImage(QQuickItem * p = 0) : QQuickPaintedItem(p), xpos(0), ypos(0) {}
    void paint(QPainter *painter) {
        if (!source.isNull()) painter->drawImage(QRect(0, 0, width(), height()), source, QRect(xpos, ypos, width(), height()));
        else painter->fillRect(QRect(0, 0, width(), height()), Qt::black);
    }
public slots:
    bool load(QString path) {
        source = QImage(path);
        return !source.isNull();
    }
    void moveBy(int x, int y) {
        int ox, oy;
        // don't go outside the image
        ox = x + xpos + width() <= source.width() ? x + xpos : source.width() - width();
        oy = y + ypos + height() <= source.height() ? y + ypos : source.height() - height();
        if (ox < 0) ox = 0;
        if (oy < 0) oy = 0;
        if (ox != xpos || oy != ypos) {
            xpos = ox;
            ypos = oy;
            update();
        }
    }
private:
    QImage source;
    int xpos, ypos;
};

And on the QML side:

PImage {
    width: 300
    height: 300
    Component.onCompleted: load("d:/img.jpg") // a big image
    MouseArea {
        property int ix
        property int iy
        anchors.fill: parent
        onPressed: {
            ix = mouseX
            iy = mouseY
        }
        onPositionChanged: {
            parent.moveBy(ix - mouseX, iy - mouseY)
            ix = mouseX
            iy = mouseY
        }
    }
}

This is just a quick basic example, there is a lot of room to polish and improve. Also note that if the source rect is different size than the target rect, you can easily achieve zooming in or out. You can hook it to a flickable to get the "kinetic scrolling" instead of the mouse area.

查看更多
登录 后发表回答