How to integrate a custom GraphicsItem into a QML scene? - qt

Assume you have created the following custom QGraphicsRectItem in C++:
class MyCustomItem : public QGraphicsRectItem
{
public:
MyCustomItem(MyCustomItem* a_Parent = 0);
virtual ~MyCustomItem();
// specific methods
private:
// specific data
};
Assume also that you have defined in a QML script an ApplicationWindow:
// main.qml
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Window 2.0
ApplicationWindow {
id: myWindow
title: qsTr("My Window")
width: 640
height: 480
visible: true
}
The simple task I would like to do is to display an instance of MyCustomItem in that ApplicationWindow. I wanted to do the following:
// part of main.cpp
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
MyCustomItem* myItem;
engine.rootContext()->setContextProperty("MyCustomItem", myItem);
return app.exec();
}
But of course this doesn't work, because MyCustomItem is neither a QObject nor a QVariant. I don't want my item to be anything else than a QGraphicsRectItem. Isn't that possible to display that graphics item? That should be simple as hell, shouldn't it? Is there a way with QDeclarativeItem or something? I can't find how to solve this problem, that's very frustrating. Would I implement my application with "normal" Qt, the problem would already be solved, because in this case you have a scene, and the scene has a member method addItem() and I don't need to do complicated stuff to add my custom graphics item to my scene. Do I have to wrap this item in a QDeclarativeItem or a QObject in order to get the thing done? That would be so awful, in my opinion. Aren't there better options?
EDIT
Can that be that QGraphicsRectItem is not the right class to inherit from and that something like QQuickPaintedItem (as suggested in the comments) would be more appropriate?

I can't speak for Qt 4, but in Qt 5, you have several options for custom drawing:
QQuickPaintedItem
A QPainter-based QQuickItem. This sounds the closest to what you want. A snippet from the documentation of one of the examples:
void TextBalloon::paint(QPainter *painter)
{
QBrush brush(QColor("#007430"));
painter->setBrush(brush);
painter->setPen(Qt::NoPen);
painter->setRenderHint(QPainter::Antialiasing);
painter->drawRoundedRect(0, 0, boundingRect().width(), boundingRect().height() - 10, 10, 10);
if (rightAligned)
{
const QPointF points[3] = {
QPointF(boundingRect().width() - 10.0, boundingRect().height() - 10.0),
QPointF(boundingRect().width() - 20.0, boundingRect().height()),
QPointF(boundingRect().width() - 30.0, boundingRect().height() - 10.0),
};
painter->drawConvexPolygon(points, 3);
}
else
{
const QPointF points[3] = {
QPointF(10.0, boundingRect().height() - 10.0),
QPointF(20.0, boundingRect().height()),
QPointF(30.0, boundingRect().height() - 10.0),
};
painter->drawConvexPolygon(points, 3);
}
}
Canvas
JavaScript-based drawing QML type with an HTML5-like API. A snippet from one of the examples:
Canvas {
id: canvas
width: 320
height: 250
antialiasing: true
property color strokeStyle: Qt.darker(fillStyle, 1.2)
property color fillStyle: "#6400aa"
property int lineWidth: 2
property int nSize: nCtrl.value
property real radius: rCtrl.value
property bool fill: true
property bool stroke: false
property real px: width/2
property real py: height/2 + 10
property real alpha: 1.0
onRadiusChanged: requestPaint();
onLineWidthChanged: requestPaint();
onNSizeChanged: requestPaint();
onFillChanged: requestPaint();
onStrokeChanged: requestPaint();
onPaint: squcirle();
function squcirle() {
var ctx = canvas.getContext("2d");
var N = canvas.nSize;
var R = canvas.radius;
N=Math.abs(N);
var M=N;
if (N>100) M=100;
if (N<0.00000000001) M=0.00000000001;
ctx.save();
ctx.globalAlpha =canvas.alpha;
ctx.fillStyle = "white";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = canvas.strokeStyle;
ctx.fillStyle = canvas.fillStyle;
ctx.lineWidth = canvas.lineWidth;
ctx.beginPath();
var i = 0, x, y;
for (i=0; i<(2*R+1); i++){
x = Math.round(i-R) + canvas.px;
y = Math.round(Math.pow(Math.abs(Math.pow(R,M)-Math.pow(Math.abs(i-R),M)),1/M)) + canvas.py;
if (i == 0)
ctx.moveTo(x, y);
else
ctx.lineTo(x, y);
}
for (i=(2*R); i<(4*R+1); i++){
x =Math.round(3*R-i)+canvas.px;
y = Math.round(-Math.pow(Math.abs(Math.pow(R,M)-Math.pow(Math.abs(3*R-i),M)),1/M)) + canvas.py;
ctx.lineTo(x, y);
}
ctx.closePath();
if (canvas.stroke) {
ctx.stroke();
}
if (canvas.fill) {
ctx.fill();
}
ctx.restore();
}
}
QSGGeometryNode
As mentioned in this answer, you could take advantage of the Qt Quick Scene Graph. A snippet from one of the examples:
QSGNode *BezierCurve::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
{
QSGGeometryNode *node = 0;
QSGGeometry *geometry = 0;
if (!oldNode) {
node = new QSGGeometryNode;
geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), m_segmentCount);
geometry->setLineWidth(2);
geometry->setDrawingMode(GL_LINE_STRIP);
node->setGeometry(geometry);
node->setFlag(QSGNode::OwnsGeometry);
QSGFlatColorMaterial *material = new QSGFlatColorMaterial;
material->setColor(QColor(255, 0, 0));
node->setMaterial(material);
node->setFlag(QSGNode::OwnsMaterial);
} else {
node = static_cast<QSGGeometryNode *>(oldNode);
geometry = node->geometry();
geometry->allocate(m_segmentCount);
}
QRectF bounds = boundingRect();
QSGGeometry::Point2D *vertices = geometry->vertexDataAsPoint2D();
for (int i = 0; i < m_segmentCount; ++i) {
qreal t = i / qreal(m_segmentCount - 1);
qreal invt = 1 - t;
QPointF pos = invt * invt * invt * m_p1
+ 3 * invt * invt * t * m_p2
+ 3 * invt * t * t * m_p3
+ t * t * t * m_p4;
float x = bounds.x() + pos.x() * bounds.width();
float y = bounds.y() + pos.y() * bounds.height();
vertices[i].set(x, y);
}
node->markDirty(QSGNode::DirtyGeometry);
return node;
}
QQuickWidget
If you really want to use QGraphicsItem subclasses, you could go the opposite direction, and have a widget-based app that contains certain "Qt Quick Widgets", though this is not optimal (see Qt Weekly #16: QQuickWidget for more information).

Related

MultiColor Gradient in QML

I started learning the Qt framework, and I wanted to create a MultiColored Gradient (like below image) background for the Window:
I went through the official documentation on Linear/Conical/Radial/Shape Gradient QML type and also did Google. But couldn't see any option to achieve this.
Is there any other way to make it possible? Preferably in QML.
Edit 1:
As stated in Comments, GradientStop can be used for multiple colors. But its can't be used it to produce the result as in the given image.
At first familiarize with Qt example quick\scenegraph\fboitem. It demonstrates creating custom QML control with C++/OpenGL backend.
Also you can implement it with ShaderEffect QML control. I recommend this site https://www.shadertoy.com/new for debugging your shaders. Also you can use, for example, Canvas for passing arrays to your shader as texture (ShaderEffect doesn't allow passing arrays normally, https://bugreports.qt.io/browse/QTBUG-50493).
Here is possible implementation of your control with dynamic points count, colors and positions.
import QtQuick 2.12
import QtQuick.Controls 2.5
ApplicationWindow {
id: window
visible: true
width: 640
height: 480
title: "Custom gradient"
Item {
anchors.fill: parent
layer.enabled: true
layer.effect: ShaderEffect {
readonly property var dataTex: dataCanvas
readonly property int count: dataCanvas.colors.length
fragmentShader: "
varying highp vec2 qt_TexCoord0;
uniform lowp float qt_Opacity;
uniform sampler2D dataTex;
const uniform lowp int count;
void main() {
vec3 col = 0.0;
for (int i = 0; i < count; i++) {
vec4 color = texture2D(dataTex, vec2(i / (float)count + 0.01, 0.0));
vec2 point = texture2D(dataTex, vec2(i / (float)count + 0.01, 1.0)).rg;
float dist = distance(qt_TexCoord0, point);
col += color * (1.0 - dist);
}
gl_FragColor = vec4(col, 1.0);
}
";
}
// Because ShaderEffect doesn't allow passing arrays to shader, we will
// convert arrays to graphical representation and pass them as texture.
Canvas {
id: dataCanvas
readonly property var colors: ["cyan", "red", "green", "darkblue", "yellow"]
readonly property var positions: [Qt.point(0,0),
Qt.point(10, parent.height - 20),
Qt.point(parent.width / 2, parent.height / 4),
Qt.point(parent.width, 0),
Qt.point(parent.width, parent.height)]
height: 2
width: colors.length
antialiasing: false
visible: false
onPaint: {
if (colors.length !== positions.length) {
console.error("Array size of 'colors' doesn't equal array size of 'positions'");
return;
}
var ctx = getContext("2d");
ctx.reset();
ctx.lineWidth = 1;
for (var i = 0; i < colors.length; i++) {
ctx.beginPath();
ctx.strokeStyle = colors[i];
ctx.moveTo(i, 0);
ctx.lineTo(i+1, 0);
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.strokeStyle = Qt.rgba(positions[i].x / parent.width, positions[i].y / parent.height, 0, 1);
ctx.moveTo(i, 1);
ctx.lineTo(i+1, 1);
ctx.stroke();
ctx.closePath();
}
}
}
}
}
That is how it looks
I'm not OpenGL guru, so this code isn't perfect. I'll be glad if someone will edit this answer to make it better.
Ihor's answer is good. I found that Qt actually provides an example that may be of use as well. Check out their Squircle example. I won't reproduce it all here, but it creates a QQuickItem in C++ and does some custom OpenGL rendering on it. The object can then be used in QML.

QT Scene Graph cannot draw a grid

I want to use Qt scene graph to draw a grid. I haven't succeeded in research for a few days. Please help me, thank you!
The issue is:
Why can't I show the results?
Where do I call glViewport? or some other way?
I have followed the code and found that Qt called renderer->setViewportRect(rect) in QQuickWindowPrivate::renderSceneGraph();
But the scene graph uses the entire window as the drawing area instead of the custom QQuickItem object.
I recalculated the shader matrix, but it didn't work. I think it is ugly
source code
// grid_item.h
class GridItem : public QQuickItem
{
Q_OBJECT
public:
explicit GridItem(QQuickItem *parent = nullptr);
protected:
QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData) Q_DECL_OVERRIDE;
};
// grid_item.cpp
GridItem::GridItem(QQuickItem *parent) : QQuickItem (parent)
{
setFlag(ItemHasContents, true);
}
QSGNode *GridItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
{
QRectF rect = boundingRect();
if (rect.isEmpty()) {
delete oldNode;
return nullptr;
}
QSGGeometryNode *node = nullptr;
QSGGeometry *geometry = nullptr;
GridItemMaterial *material = nullptr;
if(!oldNode)
{
node = new QSGGeometryNode;
node->setFlags(QSGNode::OwnsGeometry | QSGNode::OwnsMaterial, true);
geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 0);
geometry->setDrawingMode(QSGGeometry::DrawLines);
node->setGeometry(geometry);
material = new GridItemMaterial;
material->setFlag(QSGMaterial::RequiresDeterminant, true);
node->setMaterial(material);
}
else
{
node = static_cast<QSGGeometryNode *>(oldNode);
geometry = node->geometry();
material = static_cast<GridItemMaterial *>(node->material());
}
int m_xAxisSegment {10};
int m_yAxisSegment {10};
const int totalVertices = (m_xAxisSegment+1)*2 + (m_yAxisSegment+1)*2;
if(geometry->vertexCount() != totalVertices)
{
geometry->allocate(totalVertices);
QSGGeometry::Point2D *vertices = geometry->vertexDataAsPoint2D();
for(int x=0; x<=m_xAxisSegment; x++)
{
float xPos = 1.0f*x/m_xAxisSegment;
(*vertices++).set(xPos, 0.0f);
(*vertices++).set(xPos, 1.0f);
}
for(int y=0; y<=m_yAxisSegment; y++)
{
float yPos = 1.0f*y/m_yAxisSegment;
(*vertices++).set(0.0f, yPos);
(*vertices++).set(1.0f, yPos);
}
node->markDirty(QSGNode::DirtyGeometry);
}
// calculate matrix for shader
ConvertParameter param;
param.windowWidth = 640;
param.windowHeight = 480;
param.contentX = 100;
param.contentY = 100;
param.contentWidth = 200;
param.contentHeight = 200;
param.glX = 0;
param.glY = 0;
param.glWidth = 1.0f;
param.glHeight = 1.0f;
material->m_convertParameter = param;
return node;
}
// grid_item_material.h
class GridItemMaterial : public QSGMaterial
{
public:
QSGMaterialType *type() const Q_DECL_OVERRIDE;
QSGMaterialShader *createShader() const Q_DECL_OVERRIDE;
ConvertParameter m_convertParameter;
};
// grid_item_material.cpp
QSGMaterialType *GridItemMaterial::type() const
{
static QSGMaterialType type;
return &type;
}
QSGMaterialShader *GridItemMaterial::createShader() const
{
return new GridItemMaterialShader;
}
// grid_item_material_shader.h
class GridItemMaterialShader : public QSGMaterialShader
{
public:
GridItemMaterialShader();
const char *const *attributeNames() const Q_DECL_OVERRIDE;
void updateState(const RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) Q_DECL_OVERRIDE;
protected:
void initialize() Q_DECL_OVERRIDE;
QMatrix4x4 getConvertMatrix(const ConvertParameter &param);
private:
int m_id_mvpMatrix {-1};
int m_id_gridlineColor {-1};
};
// grid_item_material_shader.cpp
GridItemMaterialShader::GridItemMaterialShader()
{
setShaderSourceFile(QOpenGLShader::Vertex, ":/shaders/gridlines.vert");
setShaderSourceFile(QOpenGLShader::Fragment, ":/shaders/gridlines.frag");
}
const char * const *GridItemMaterialShader::attributeNames() const
{
static char const *const names[] = { "Vertex", 0 };
return names;
}
void GridItemMaterialShader::updateState(const RenderState &state, QSGMaterial *newMaterial, QSGMaterial *)
{
GridItemMaterial *material = static_cast<GridItemMaterial *>(newMaterial);
QMatrix4x4 matrix = getConvertMatrix(material->m_convertParameter);
program()->setUniformValue(m_id_mvpMatrix, matrix);
program()->setUniformValue(m_id_gridlineColor, QColor::fromRgbF(1, 0, 0, 1));
}
void GridItemMaterialShader::initialize()
{
m_id_mvpMatrix = program()->uniformLocation("mvpMatrix");
m_id_gridlineColor = program()->uniformLocation("gridlineColor");
}
QMatrix4x4 GridItemMaterialShader::getConvertMatrix(const ConvertParameter &param)
{
QMatrix4x4 model1;
// convert window to (-1, -1)..(+1, +1)
model1.setToIdentity();
model1.translate(-1, -1, 0);
model1.scale(2.0f/param.windowWidth, 2.0f/param.windowHeight, 1.0f);
// left-bottom
QVector4D v3(param.contentX, param.windowHeight-param.contentY-param.contentHeight, 0, 1);
v3 = model1 * v3;
// right-top
QVector4D v4(param.contentX+param.contentWidth, param.windowHeight-param.contentY, 0, 1);
v4 = model1 * v4;
// content area should in (-1, -1)..(+1, +1)
float width = v4.x() - v3.x();
float height = v4.y() - v3.y();
QMatrix4x4 model2;
model2.setToIdentity();
model2.translate(v3.x(), v3.y(), 0);
model2.scale(width/param.glWidth, height/param.glHeight, 1);
model2.translate(-param.glX, -param.glY, 0);
return model2;
}
// grid_convert_parameter.h
struct ConvertParameter
{
int windowWidth = 640;
int windowHeight = 480;
int contentX = 100;
int contentY = 100;
int contentWidth = 200;
int contentHeight = 200;
float glX = 3;
float glY = 3;
float glWidth = 4.0f;
float glHeight = 4.0f;
};
// main.cpp
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
qmlRegisterType<GridItem>("io.draw", 1, 0, "GridItem");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
QQuickWindow *window = static_cast<QQuickWindow *>(engine.rootObjects().first());
QSurfaceFormat format = window->requestedFormat();
format.setProfile(QSurfaceFormat::CoreProfile);
format.setVersion(3, 3);
window->setFormat(format);
window->show();
return app.exec();
}
// main.qml
import QtQuick 2.9
import QtQuick.Controls 2.4
import io.draw 1.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
GridItem {
x: 100
y: 100
width: 200
height: 200
}
}
// gridlines.vert
#version 330 core
uniform mat4 mvpMatrix;
layout(location = 0) in vec2 Vertex;
void main(void)
{
gl_Position = mvpMatrix * vec4(Vertex, 0.0, 1.0);
}
// gridlines.frag
#version 330 core
uniform vec4 gridlineColor;
layout(location = 0) out vec4 fragColor;
void main(void)
{
fragColor = gridlineColor;
}
I have also made a simple change based on the Qt OpenGL demo.
class OpenGLWindow : public QWindow, protected QOpenGLFunctions_3_3_Core
Almost done the same thing, except that the results are output directly to the entire window (but this is not what I want)
Another difference is the transformation matrix changed:
QMatrix4x4 model, view, projection;
projection.ortho(0, 1, 0, 1, -10, 10);
m_program->setUniformValue(m_matrixUniform, projection*view*model);
It works properly...
Because it involves OpenGL and Qt Scene Graph, I don't know what went wrong.

Reallocating QSGGeometryNode Vertex Data

I am using Qt 5.8. I am attempting to draw circular points using QSGGeometry::DrawTriangles (using QSGGeometry::DrawPoints draws points as rectangles/squares--not desired). In order to do this I am drawing 8 triangles that gives the illusion of a circle. Each data point will have 8 triangles associated with it. The number of data points can vary at any given time. After a (user) specified amount of time as a data point is added, one data point is removed. There seems to be an error in the allocation of data when it's drawn. I used setVertexDataPattern(QSGGeometry::StreamPattern); in the construction of the QSGGeometryNode; in hopes of getting the desired output.
On each draw call, I call m_geometry.allocate(m_pts.size() * MAX_VERTICES), where MAX_VERTICES = 24 in case the number of points since the last draw call has changed. I have attempted to use GL_POLYGON (since it would require fewer vertices), but the same problem happens. There seems to be an attempt to draw a shape from one slice of the the first data point to another slice of the last data point. Is there something wrong with reallocating for every draw call? What is the proper way to handle drawing data with varying sizes?
Update I think it may deal with a size issue. I have sample code that only draws 1 triangle (instead of 8) and once you get to about 25000 (times 3 for each triangle) the odd line appears and seems to stop drawing additional triangles. In the following sample code (when using a smaller number of points) the last triangle is white.
main.qml
import QtQuick 2.8
import QtQuick.Window 2.2
import TestModule 1.0
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Rectangle {
color: "black"
width: parent.width
height: parent.height * .90
anchors.top: parent.top
MouseArea {
anchors.fill: parent
onClicked: {
Qt.quit();
}
}
TestItem {
id: testItem
anchors.fill: parent
ptCount: 25000
color: "green"
}
}
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
height: parent.height * .10
width: parent.width
border.color: "pink"
color: "lightgray"
TextInput {
anchors.fill: parent
anchors.centerIn: parent
id: textInput
text: "enter max number of points here"
horizontalAlignment: TextInput.AlignHCenter
verticalAlignment: TextInput.AlignVCenter
color: "steelblue"
onEditingFinished: testItem.ptCount = parseInt(textInput.text)
validator: IntValidator{bottom: 1}
}
}
}
TestItem.h
#include <QQuickItem>
class QSGGeometryNode;
class TestItem : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
Q_PROPERTY(qint32 ptCount READ ptCount WRITE setPtCount NOTIFY ptCountChanged)
public:
explicit TestItem(QQuickItem *parent = 0);
QColor color();
void setColor(const QColor &color);
void setPtCount(const qint32& newVal);
qint32 ptCount();
signals:
void colorChanged();
void ptCountChanged();
protected:
QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *);
void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry);
QColor m_color;
qint32 m_ptCount;
};
TestItem.cpp
#include "TestItem.h"
#include <QSGNode>
#include <QSGVertexColorMaterial>
TestItem::TestItem(QQuickItem *parent) : QQuickItem(parent), m_color(Qt::green), m_ptCount(25000)
{
setFlag(ItemHasContents, true);
}
QColor TestItem::color()
{
return m_color;
}
void TestItem::setColor(const QColor &color)
{
m_color = color;
update();
emit colorChanged();
}
void TestItem::setPtCount(const qint32 &newVal)
{
if (newVal < 0)
m_ptCount = 25000;
else
m_ptCount = newVal;
update();
emit ptCountChanged();
}
qint32 TestItem::ptCount()
{
return m_ptCount;
}
QSGNode *TestItem::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *)
{
QSGGeometryNode *node = nullptr;
QSGGeometry *geometry = nullptr;
if (!oldNode)
{
node = new QSGGeometryNode;
geometry = new QSGGeometry(QSGGeometry::defaultAttributes_ColoredPoint2D(), m_ptCount * 3);
geometry->setDrawingMode(GL_TRIANGLES);
node->setGeometry(geometry);
node->setFlag(QSGNode::OwnsGeometry);
QSGVertexColorMaterial *material = new QSGVertexColorMaterial;
node->setMaterial(material);
node->setFlag(QSGNode::OwnsMaterial);
}
else
{
node = static_cast<QSGGeometryNode *>(oldNode);
geometry = node->geometry();
geometry->allocate(m_ptCount * 3);
}
QSGGeometry::ColoredPoint2D *vertices = geometry->vertexDataAsColoredPoint2D();
qreal triWidth = 250/boundingRect().width() + 10;
for (int i = 0; i < m_ptCount; ++i)
{
QColor color;
if (i == m_ptCount - 1)
color = Qt::white;
else
color = m_color;
qreal x0 = (boundingRect().width() * .90/m_ptCount) * i ;
qreal y0 = 60 * sinf(x0* 3.14/180); // 60 just varies the height of the wave
qreal x1 = x0 + 0.05 * boundingRect().width(); // 0.05 so that we have 5% space on each side
qreal y1 = y0 + boundingRect().height()/2;
vertices[i * 3].set(x1, y1, color.red(), color.green(), color.blue(), color.alpha());
vertices[i * 3 + 1].set(x1 + triWidth, y1, color.red(), color.green(), color.blue(), color.alpha());
vertices[i * 3 + 2].set(x1 + triWidth, y1 + triWidth, color.red(), color.green(), color.blue(), color.alpha());
}
node->markDirty(QSGNode::DirtyGeometry);
return node;
}
void TestItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
{
update();
QQuickItem::geometryChanged(newGeometry, oldGeometry);
}
Any help with determining if this is a Qt bug or my error somewhere?
It turns out I needed to change the geometry's constructor to use UnsignedIntType; it defaults to UnsignedShortType.
geometry = new QSGGeometry(QSGGeometry::defaultAttributes_ColoredPoint2D(), m_ptCount * 3, 0, QSGGeometry::UnsignedIntType);

QQuickWidget with transparent background

I added in a QDialog aQQuickWidget to load a .qml but its background color is white. My qml declares a Rectangle not filling all QQuickWidget surface. I need the not filled surface having the same background color of the dialog. What is the way to have a transparent background?
Floating cloud:
auto quickWidget = new QQuickWidget();
quickWidget->setWindowFlags(Qt::SplashScreen);
quickWidget->setAttribute(Qt::WA_AlwaysStackOnTop);
quickWidget->setAttribute(Qt::WA_TranslucentBackground);
quickWidget->setClearColor(Qt::transparent);
quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
quickWidget->setSource(QUrl("qrc:/cloud.qml"));
quickWidget->show();
cloud.qml
import QtQuick 2.0
Item {
id: root
width: 400
height: 300
Canvas {
id: cloud
anchors.fill: parent
onPaint: {
var ctx = getContext("2d");
ctx.beginPath();
var x = 100;
var y = 170;
ctx.arc(x, y, 60, Math.PI * 0.5, Math.PI * 1.5);
ctx.arc(x + 70, y - 60, 70, Math.PI * 1, Math.PI * 1.85);
ctx.arc(x + 152, y - 45, 50, Math.PI * 1.37, Math.PI * 1.91);
ctx.arc(x + 200, y, 60, Math.PI * 1.5, Math.PI * 0.5);
ctx.moveTo(x + 200, y + 60);
ctx.lineTo(x, y + 60);
ctx.strokeStyle = "#797874";
ctx.stroke();
ctx.fillStyle = "#8ED6FF";
ctx.fill();
}
}
}
It's explained in the QQuickWidget documentation : QQuickWidget Limitations
Putting other widgets underneath and making the QQuickWidget transparent will not lead to the expected results: the widgets underneath will not be visible. This is because in practice the QQuickWidget is drawn before all other regular, non-OpenGL widgets, and so see-through types of solutions are not feasible. Other type of layouts, like having widgets on top of the QQuickWidget, will function as expected.
When absolutely necessary, this limitation can be overcome by setting the Qt::WA_AlwaysStackOnTop attribute on the QQuickWidget. Be aware, however that this breaks stacking order. For example it will not be possible to have other widgets on top of the QQuickWidget, so it should only be used in situations where a semi-transparent QQuickWidget with other widgets visible underneath is required.
Use QQuickWidget::setClearColor(Qt::transparent) and set an alpha channel via the setFormat call.
project.pro
QT += core gui widgets qml quickwidgets
CONFIG += c++17
main.cpp
#include <QApplication>
#include <QObject>
#include <QDialog>
#include <QVBoxLayout>
#include <QtQuickWidgets/QQuickWidget>
/*****************************************************************************/
void show (QUrl component) {
QDialog* dialog = new QDialog();
dialog->setStyleSheet("background-color: green;");
QObject::connect(
QCoreApplication::instance(),
SIGNAL(aboutToQuit()),
dialog,
SLOT(deleteLater())
);
/*QObject::connect(dialog,&QObject::destroyed,[](){
qDebug() << "destroyed";
});*/
QQuickWidget* content = new QQuickWidget(component, dialog);
content->setResizeMode(QQuickWidget::SizeRootObjectToView);
content->setAttribute(Qt::WA_AlwaysStackOnTop);
content->setClearColor(Qt::transparent);
dialog->show();
}
/*****************************************************************************/
int main(int argc, char *argv[]) {
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication a(argc, argv);
show(QStringLiteral("qrc:/qml/Button.qml"));
show(QStringLiteral("qrc:/qml/Button.qml"));
show(QStringLiteral("qrc:/qml/Button.qml"));
show(QStringLiteral("qrc:/qml/Button.qml"));
return a.exec();
}
/*****************************************************************************/
Button.qml
import QtQuick 2.12
import QtQuick.Controls 2.5
Button {
height: 100
width: 100
text: "lol"
}
result
also try gradient background
dialog->setStyleSheet(
"background-color: qlineargradient(spread:pad,x1:0,y1:1,x2:0,y2:0," \
" stop:0 rgba(34, 34, 44, 255)," \
" stop:1 rgba(56, 55, 72, 255)" \
");"
);

Issue with drawing an Qml Item with raw OpenGL calls

I want to draw a single item in QtQuick scene using raw OpenGL calls. I have decided to take approach suggested in this question.
I have created a Qt Quick item deriving from QQuickFramebufferObject and exposed it to QML as Renderer: (code is based on Qt example: Scene Graph - Rendering FBOs)
class FboInSGRenderer : public QQuickFramebufferObject {
Q_OBJECT
public:
Renderer *createRenderer() const;
};
source file:
class LogoInFboRenderer : public QQuickFramebufferObject::Renderer {
public:
LogoInFboRenderer() { }
void render() {
int width = 1, height = 1;
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glColor4f(0.0, 1.0, 0.0, 0.8);
glBegin(GL_QUADS);
glVertex2f(0, 0);
glVertex2f(width, 0);
glVertex2f(width, height);
glVertex2f(0, height);
glEnd();
glLineWidth(2.5);
glColor4f(0.0, 0.0, 0.0, 1.0);
glBegin(GL_LINES);
glVertex2f(0, 0);
glVertex2f(width, height);
glVertex2f(width, 0);
glVertex2f(0, height);
glEnd();
update();
}
QOpenGLFramebufferObject *createFramebufferObject(const QSize &size) {
QOpenGLFramebufferObjectFormat format;
format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
format.setSamples(4);
return new QOpenGLFramebufferObject(size, format);
}
};
QQuickFramebufferObject::Renderer *FboInSGRenderer::createRenderer() const {
return new LogoInFboRenderer();
}
In Qml I use it as follows:
import QtQuick 2.4
import SceneGraphRendering 1.0
Rectangle {
width: 400
height: 400
color: "purple"
Renderer {
id: renderer
anchors.fill: parent
}
}
I was expecting to see that rendered "X" will fill entire scene, but instead I get the result presented below:
Other experiments seem to confirm that drew shape has always it's size (width/height) divided by 2.
I also checked that size parameter in createFramebufferObject has correct value.
Looking into docs led me to property textureFollowsItemSize in QQuickFramebufferObject class but it is by default set to true.
Am I doing something wrong or should I consider this behavior as Qt bug?
The drawn rectangle is half the sizes you expect because the default coordinate range is [-1, 1], not [0, 1] as your code assumes. If you want to use [0, 1] scale, then you should appropriately set the projection matrix:
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0, 1.0, 0.0, 1.0, -1.0, 1.0);
As Qt documentation says: "Warning: It is crucial that OpenGL operations and interaction with the scene graph happens exclusively on the rendering thread, primarily during the updatePaintNode() call. The best rule of thumb is to only use classes with the "QSG" prefix inside the QQuickItem::updatePaintNode() function." I do it in this way:
*.h
class MyQuickItem : public QQuickItem
{
Q_OBJECT
public:
MyQuickItem();
~MyQuickItem();
protected:
QSGNode *updatePaintNode(QSGNode * oldNode, UpdatePaintNodeData * updatePaintNodeData);
QSGNode *addNode(QSGGeometry *geometry, const QColor &color);
};
*.cpp
MyQuickItem::MyQuickItem()
{
setFlag(QQuickItem::ItemHasContents,true);
}
MyQuickItem::~MyQuickItem()
{
}
QSGNode *MyQuickItem::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *updatePaintNodeData)
{
Q_UNUSED(updatePaintNodeData)
QSGTransformNode *root = static_cast<QSGTransformNode *>(oldNode);
if(!root) root = new QSGTransformNode;
QSGNode *node;
QSGGeometry *geometry;
QSGSimpleRectNode *rect = new QSGSimpleRectNode();
rect->setColor(Qt::green);
rect->setRect(boundingRect());
root->appendChildNode(rect);
geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 2);
geometry->setDrawingMode(GL_LINES);
geometry->setLineWidth(5.0);
geometry->vertexDataAsPoint2D()[0].set(x(), y());
geometry->vertexDataAsPoint2D()[1].set(width(), height());
node = addNode(geometry,Qt::blue);
root->appendChildNode(node);
geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 2);
geometry->setDrawingMode(GL_LINES);
geometry->setLineWidth(5.0);
geometry->vertexDataAsPoint2D()[0].set(width(), y());
geometry->vertexDataAsPoint2D()[1].set(x(), height());
node = addNode(geometry,Qt::blue);
root->appendChildNode(node);
return root;
}
QSGNode *MyQuickItem::addNode(QSGGeometry *geometry, const QColor &color)
{
QSGFlatColorMaterial *material = new QSGFlatColorMaterial;
material->setColor(color);
QSGGeometryNode *node = new QSGGeometryNode;
node->setGeometry(geometry);
node->setFlag(QSGNode::OwnsGeometry);
node->setMaterial(material);
node->setFlag(QSGNode::OwnsMaterial);
return node;
}
In main.cpp
qmlRegisterType<MyQuickItem>("MyObjects", 1, 0, "MyObject");
And usage:
import QtQuick 2.3
import QtQuick.Window 2.2
import MyObjects 1.0
Window {
visible: true
width: 360
height: 360
MyObject {
anchors.fill: parent
}
}

Resources