I'm new to Qt, and from what I've read on qt-project.org and other places; QtQuick seems like an attractive option because of its ability to work on both pointer and touch based devices. My problem is getting it to work well with c++.
I decided to write a variant of Conway's Game of Life as a next step after "Hello World". I am thoroughly mystified as to how to get the "board" -- a [height][width][bytes-per-pixel] array of char -- integrated into the scene graph.
Basically, the process is that the "LifeBoard" iterates through its rules and updates the char*/image. I've got this simple QML:
:::QML
ApplicationWindow {
id: life_app_window
visible: true
title: qsTr("Life")
menuBar: MenuBar {
Menu {
title: qsTr("File")
MenuItem {
text: qsTr("Quit")
onTriggered: Qt.quit();
}
}
}
toolBar: ToolBar {
id: lifeToolBar;
ToolButton {
id: toolButtonQuit
text: qsTr("Quit")
onClicked: Qt.quit()
}
ToolButton {
id: toolButtonStop
text: qsTr("Stop")
enabled: false
//onClicked:
}
ToolButton {
id: toolButtonStart
text: qsTr("Start")
enabled: true
//onClicked: //Start life.
}
ToolButton {
id: toolButtonReset
text: qsTr("Stop")
// onClicked: //Reset life.
}
}
Flow {
id: flow1
anchors.fill: parent
//*****
// WHAT GOES HERE
//*****
}
statusBar: StatusBar {
enabled: false
Text {
// Get me from number of iterations
text: qsTr("Iterations.")
}
}
}
I want to image to come from a class with a api kinda like this:
class Life {
public:
QImage getImage() {}
// Or
char* getPixels(int h, int w, QImage::Format_ARGB8888) {}
}
I have no clue, and hours wading through tutorials did not help. How does one link a char* image in c++ to a ??? in QML so that the QML can start/stop the "Life" loop and so that the "Life" loop and update the char array and notify QML to redraw it?
Note: I've looked at subclassing QQuickImageProvider based on the info here. The problem with this approach is that I cannot see how to let c++ "drive" the on screen image. I wish to pass control from QML to c++ and let c++ tell QML when to update the display with the changed image. Is there a solution with this approach? Or another approach entirely.
First way to do that would be creating a Rectangle for each game pixel in QML, which might be fancy for a 8x8 board, but not for a 100x100 board, since you need to write the QML code manually for each pixel.
Thus I'd go for images created in C++ and exposed to QML. You call them via an image provider to allow asynchronous loading. Let Life
do the logic only.
The image is called from QML like this:
Image {
id: board
source: "image://gameoflife/board"
height: 400
width: 400
}
Now gameoflife
is the name of the image provider and board
the so-called id
you can use later.
Register gameoflife
in you main.cpp
LifeImageProvider *lifeIP = new LifeImageProvider(life);
engine.addImageProvider("gameoflife", lifeIP);
where engine
is your main QQmlApplicationEngine
and life
an instance of your Life
game engine.
LifeImageProvider
is your class to create pixeldata. Starts somehow like
class LifeImageProvider : public QQuickImageProvider
{
public:
LifeImageProvider(Life *myLifeEngine);
QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize);
private:
Life *myLifeEngine_;
};
The important method is requestPixmap, which is called from QML. You need to implement it.
To refresh the game board when Life
sends a stateChanged()
signal, expose life
as a global object to QML:
context->setContextProperty("life", &life);
You can bind the signal to QML
Image {
id: board
source: "image://gameoflife/board"
height: 400
width: 400
}
Connections {
target: life
onStateChanged: {
board.source = "image://gameoflife/board?" + Math.random()
// change URL to refresh image. Add random URL part to avoid caching
}
}