PDA

View Full Version : help on QSGGeometry



joko
2nd April 2015, 11:28
Hi,

I would like to know some advise on creating a custom geometry using scene graph.
My problem is I don't know how to fill the custom geometry with color.
And how to add text on geometry node without using QPainter, is it possible?

Below is my code:



QSGNode *TabBar::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
{
QSGNode *root = static_cast<QSGNode *>(oldNode);
if(!root) root = new QSGNode;
root->removeAllChildNodes();

QSGGeometry *geometry;

geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D (), 6);
geometry->setDrawingMode(GL_TRIANGLE_FAN);
geometry->vertexDataAsPoint2D()[0].set(20, 40);
geometry->vertexDataAsPoint2D()[1].set(0, 0);
geometry->vertexDataAsPoint2D()[2].set(100, 0);
geometry->vertexDataAsPoint2D()[3].set(120, 40);
geometry->vertexDataAsPoint2D()[4].set(100, 80);
geometry->vertexDataAsPoint2D()[5].set(0, 80);
node = drawPolygon(geometry, Qt::red);
root->appendChildNode(node);
}
QSGNode *TabBar::drawPolygon(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;
}


Update:
I managed to fill the geometry using GL_TRIANGLE_FAN drawingMode and by modifying the vertices which will start on the vertex which will create a triangle without intersecting each other.

The problem now is how to draw a text on the geometry.

Please advise. TIA.

joko
3rd April 2015, 16:42
update:

Found this thread that there is no available text/glyph node yet for text rendering on the scene graph node.

http://comments.gmane.org/gmane.comp.lib.qt.user/13735

It was suggested (but not ideal) to draw text using QPainter on QImage and show on texture.
Any advise how to convert the image into texture? I can't find any example. Thanks.

wysota
3rd April 2015, 21:20
My problem is I don't know how to fill the custom geometry with color.
All OpenGL rules apply. If you construct geometry made of polygons, these polygons are filled with the current material.


And how to add text on geometry node without using QPainter, is it possible?
Yes, but I don't see why you couldn't simply compose your item from your custom geometry and a regular Text item.

Removing child nodes is wrong. You should update existing nodes if possible instead of recreating everything from scratch.

Basically I think you are doing something terribly wrong here. Why are you trying to implement this custom item?

d_stranz
3rd April 2015, 21:45
QSGNode *root = static_cast<QSGNode *>(oldNode);

And this makes no sense either. "oldNode" is already a QSGNode pointer, so what is the point of the static cast? Aside from that, this code won't compile anyway, because updatePaintNode() needs a return value.

joko
10th April 2015, 08:59
All OpenGL rules apply. If you construct geometry made of polygons, these polygons are filled with the current material.


Yes, but I don't see why you couldn't simply compose your item from your custom geometry and a regular Text item.

Removing child nodes is wrong. You should update existing nodes if possible instead of recreating everything from scratch.

Basically I think you are doing something terribly wrong here. Why are you trying to implement this custom item?

Thank you for your reply wysota.

The previous code posted was just my test as i am trying to create a polygon using qsggeometry.

Here is my code:



QSGNode *TabBar::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
{
QSGNode *root = static_cast<QSGNode *>(oldNode);
if(!root) root = new QSGNode;
root->removeAllChildNodes();

int pos = 0;
int missingWidth = missingWidth_;

int s = 0;
if (missingWidth > 0 && current_ > s)
{
missingWidth += dotsWidth_;
for (; s < current_; ++s)
{
missingWidth -= labelsWidth_[labels_[s]] + (s == 0 ? 0 : border_);
if (missingWidth <= 0)
{
++s;
break;
}
}
}

int e = labels_.size() - 1; // this will be the last element to be drawn normally
if (missingWidth > 0 && current_ < e)
{
missingWidth += dotsWidth_;
for (; e > current_; --e)
{
missingWidth -= labelsWidth_[labels_[e]] + border_;
if (missingWidth <= 0)
{
--e;
break;
}
}
}

int additionalWidthPerLabel = (additionalWidth_ + abs(missingWidth)) / (e - s + 1);

int height = TabBar::height();

int width;
bool active;
LabelPosition position;

if (s != 0)
{
paintBox(root, pos, "...", QSizeF(dotsWidth_, height), true, FirstLabel);

pos += dotsWidth_ + border_;
}
for (int i = s; i <= e; i++)
{
width = labelsWidth_[labels_[i]] + additionalWidthPerLabel;
active = (i <= current_);
position = (i == 0 ? FirstLabel : (i == (labels_.size() - 1) ? LastLabel : MiddleLabel) );

paintBox(root, pos, labels_[i], QSizeF(width, height), active, position);

pos += width + border_;
}
if (e != (labels_.size() - 1) )
{
paintBox(root, pos, "...", QSizeF(dotsWidth_, height), false, LastLabel);
}

return root;
}

void TabBar::paintBox(QSGNode *root, qreal pos, const QString &text, const QSizeF &size, bool active, LabelPosition labelPosition)
{

qreal width = size.width();
qreal height = size.height();

int arrow = height / 2;

QColor backgroundColor = active ? activeBg_ : inactiveBg_;
QColor textColor = active ? activeFg_ : inactiveFg_;

QSGNode *node;
QSGGeometry *geometry;

switch (labelPosition)
{
case FirstLabel:
geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D (), 5);
geometry->setDrawingMode(GL_TRIANGLE_FAN);
geometry->vertexDataAsPoint2D()[0].set(pos, 0);
geometry->vertexDataAsPoint2D()[1].set(pos + width, 0);
geometry->vertexDataAsPoint2D()[2].set(pos + arrow + width, height/2);
geometry->vertexDataAsPoint2D()[3].set(pos + width, height);
geometry->vertexDataAsPoint2D()[4].set(pos, height);
break;

case LastLabel:
geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D (), 5);
geometry->setDrawingMode(GL_TRIANGLE_FAN);
geometry->vertexDataAsPoint2D()[0].set(pos + arrow, height/2);
geometry->vertexDataAsPoint2D()[1].set(pos, 0);
geometry->vertexDataAsPoint2D()[2].set(pos + width, 0);
geometry->vertexDataAsPoint2D()[3].set(pos + width, height);
geometry->vertexDataAsPoint2D()[4].set(pos, height);
break;

default:
geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D (), 6);
geometry->setDrawingMode(GL_TRIANGLE_FAN);
geometry->vertexDataAsPoint2D()[0].set(pos + arrow, height/2);
geometry->vertexDataAsPoint2D()[1].set(pos, 0);
geometry->vertexDataAsPoint2D()[2].set(pos + width, 0);
geometry->vertexDataAsPoint2D()[3].set(pos + arrow + width, height/2);
geometry->vertexDataAsPoint2D()[4].set(pos + width, height);
geometry->vertexDataAsPoint2D()[5].set(pos, height);
break;
}

node = drawPolygon(geometry, backgroundColor);
root->appendChildNode(node);
}

QSGNode *TabBar::drawPolygon(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;
}



The reason I removed all the child nodes because it will overlapped the bar once updated.

I need to put the text at the center of each polygon. Please advise. TIA.

joko
10th April 2015, 15:59
I found this option to render text using QML text which would be implemented using updatePolish() function.
This code actually don't work.



QQuickItem *root = view.rootObject();

QmlEngine *engine = qmlEngine(this);
QQmlComponent component(engine);
QString str = "Text { text:" + text + "}";
QByteArray data(str);
component.setData(data, QUrl());

QQuickItem* item = qobject_cast<QQuickItem*>(component.create());
item->setParentItem(root);


My question is how to render it into my custom geometry.
I can't find any example around.
Please advise. Thanks.

wysota
10th April 2015, 21:53
Don't make your own text item. Render the text by centering a Text item over your custom item in QML.

joko
13th April 2015, 11:49
Don't make your own text item. Render the text by centering a Text item over your custom item in QML.

You mean i don't need to create a new qmlcomponent for Text element? And render the text in qml file?

If i understood you correctly on the second statement, I think that is not possible because the size of each custom geometry node depends on the length of the text.
That is why I have to render it along with the nodes.

wysota
13th April 2015, 11:52
If i understood you correctly on the second statement, I think that is not possible
Of course it is possible.


because the size of each custom geometry node depends on the length of the text.
That is why I have to render it along with the nodes.

Text item knows its size so make your custom tab dependent on that size.

MyCustomItem {
width: txt.width+20
height: txt.height+10
Text { id: txt; anchors.centerIn: parent; text: "BBB" }
}

joko
13th April 2015, 12:14
Of course it is possible.

Text item knows its size so make your custom tab dependent on that size.


My custom TabBar is a QSGNode with some QSGGeometryNodes as child.
The text should be rendered in each geometry nodes.

Here's the sample.

11081

wysota
13th April 2015, 13:34
And what exactly is the problem? You are making things more complicated than they really are.

joko
13th April 2015, 13:56
And what exactly is the problem? You are making things more complicated than they really are.

I just want to render the text after creating the geometry in paintBox function (from the previous code posted).

wysota
13th April 2015, 14:06
Excuse my crude implementation of BarItem (no borders and no AA).

//BarItem.qml
import QtQuick 2.0

ShaderEffect {
id: root
property color color
property bool first: false
mesh: GridMesh { resolution: Qt.size(1, 2) }

vertexShader: "
uniform highp mat4 qt_Matrix;
attribute highp vec4 qt_Vertex;
attribute highp vec2 qt_MultiTexCoord0;
varying highp vec2 qt_TexCoord0;
uniform highp float height;
uniform lowp vec4 color;
uniform bool first;

void main() {
highp vec4 pos = qt_Vertex;
if((!first || pos.x > 0.0) && ((pos.y > 0.0) && (pos.y < height))) pos.x = pos.x+0.5*height;
gl_Position = qt_Matrix * pos;
qt_TexCoord0 = qt_MultiTexCoord0;
}
"
fragmentShader: "
uniform lowp vec4 color;
varying highp vec2 qt_TexCoord0;
void main() {
gl_FragColor = color;
}
"
}

import QtQuick 2.0

Item {
width: 1200
height: 300

Row {
anchors.centerIn: parent
Repeater {
model: [ "blue", "red", "yellow", "orange", "purple" ]
BarItem {
color: modelData
width: 200
height: 50
first: index == 0
Text {
anchors.centerIn: parent
text: modelData
}
}
}
}
}

http://www.qtcentre.org/attachment.php?attachmentid=11082

joko
21st April 2015, 15:19
Excuse my crude implementation of BarItem (no borders and no AA).

Thanks wysota. I tried understanding the GLSL, but it seems complicated to me.

I decided to just create an arrow class that would be displayed using repeater and render text using Text QML type.

The arrow's color changes once active and if not all elements will fit on the row, an arrow with dotted text will be added either at the last (there were missing elements) or at first (there were prior elements missing). The arrow's width depends on the length of the text.

I'm having problem how to determining the total width of all elements and comparing it into the row width. Because from there I can determine the remaining width available for dotted element. And modify the model elements if needs to add a dotted element and drop the remaining elements. Then once the current active index increments, remove the dotted element and replace it with the previous missing element, the same thing on the first element, hide it and replace with dotted element. After establishing the model, repeater will then start rendering its delegate items.

Am I making any sense? Is this even possible?

Please advise another option how to possibly implement such. TIA.

ps: this has been implemented already using QQuickPaintedItem, however due to a bug on iOS (blurred render), i'm trying to implement it using QQuickItem with Text QML element.

wysota
21st April 2015, 20:49
Thanks wysota. I tried understanding the GLSL, but it seems complicated to me.
Never mind GLSL, it was just to quickly obtain something similar to what you had.


I decided to just create an arrow class that would be displayed using repeater and render text using Text QML type.
Great :)


The arrow's color changes once active and if not all elements will fit on the row, an arrow with dotted text will be added either at the last (there were missing elements) or at first (there were prior elements missing). The arrow's width depends on the length of the text.

I'm having problem how to determining the total width of all elements and comparing it into the row width. Because from there I can determine the remaining width available for dotted element. And modify the model elements if needs to add a dotted element and drop the remaining elements.
I would not modify the model.


Then once the current active index increments, remove the dotted element and replace it with the previous missing element, the same thing on the first element, hide it and replace with dotted element. After establishing the model, repeater will then start rendering its delegate items.

Am I making any sense? Is this even possible?
It is possible but I see no reason to modify your data model.


Please advise another option how to possibly implement such. TIA.
Element width is determined by the view (by size of the font and text to be drawn). Thus everything needs to be implemented in the view. The simplest approach I can think of is to put all items in an item to form a row, then put that row into another item with clipping enabled and size forced to the desired size of the bar so that only those arrows which fit into the viewport are visible. Then overlay the dotted arrow on top of that to cover the leftmost or rightmost visible element, depending on your needs.

Another approach would be to put all items in a row and based on the "active" element depend which items should be visible and simply make all other hidden. Finally the dotted arrow should be positioned where it is needed.

Of course all that should be done in a declarative way :)

joko
27th April 2015, 16:58
Of course all that should be done in a declarative way :)

Hi wysota,

Please bear with me for I have some additional questions.

Here's my current code:



// WizardBar.cpp code snippet
QSGNode *WizardBar::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
{
QSGNode *root = static_cast<QSGNode *>(oldNode);
if(!root) root = new QSGNode;

QSGGeometry *geometry;

int height = WizardBar::height();
int width = WizardBar::width();

int arrow = height / 2;

int pos = 0;

QColor backgroundColor = active_ ? activeBg_ : inactiveBg_;

switch (position_)
{
case FirstLabel:
geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D (), 5);
geometry->setDrawingMode(GL_TRIANGLE_FAN);
geometry->vertexDataAsPoint2D()[0].set(pos, 0);
geometry->vertexDataAsPoint2D()[1].set(pos + width, 0);
geometry->vertexDataAsPoint2D()[2].set(pos + arrow + width, arrow);
geometry->vertexDataAsPoint2D()[3].set(pos + width, height);
geometry->vertexDataAsPoint2D()[4].set(pos, height);
break;

case LastLabel:
geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D (), 5);
geometry->setDrawingMode(GL_TRIANGLE_FAN);
geometry->vertexDataAsPoint2D()[0].set(pos + arrow, arrow);
geometry->vertexDataAsPoint2D()[1].set(pos, 0);
geometry->vertexDataAsPoint2D()[2].set(pos + width, 0);
geometry->vertexDataAsPoint2D()[3].set(pos + width, height);
geometry->vertexDataAsPoint2D()[4].set(pos, height);
break;

default:
geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D (), 6);
geometry->setDrawingMode(GL_TRIANGLE_FAN);
geometry->vertexDataAsPoint2D()[0].set(pos + arrow, arrow);
geometry->vertexDataAsPoint2D()[1].set(pos, 0);
geometry->vertexDataAsPoint2D()[2].set(pos + width, 0);
geometry->vertexDataAsPoint2D()[3].set(pos + arrow + width, arrow);
geometry->vertexDataAsPoint2D()[4].set(pos + width, height);
geometry->vertexDataAsPoint2D()[5].set(pos, height);
break;
}

root->appendChildNode(drawPolygon(geometry, backgroundColor));

return root;
}

QSGNode *WizardBar::drawPolygon(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;
}






// qml page
Item {
id: container

property int conWidth: StaticData.screenWidth - header.anchors.leftMargin*2; // i can't get the implicit width of anchored items
property int missingWidth: 0
property int extraWidth: 0

function getWidth(rowWidth) {
if (rowWidth <= conWidth) {
missingWidth = 0;
extraWidth = conWidth - rowWidth;
} else {
extraWidth = 0;
missingWidth = rowWidth - conWidth
}
}

anchors {
right: parent.right
left: parent.left
bottom: parent.bottom
}
clip: true
height: 60

Row {
id: barRow

property int rowWidth
property int curIdx

spacing: 1

ListModel {
id: barModel

ListElement {
label: qsTr("Protocol")
group: "protocols"
}
ListElement {
label: qsTr("Host")
group: "host"
}
ListElement {
label: qsTr("Login")
group: "login"
}
ListElement {
label: qsTr("Proxy")
group: "proxy"
}
ListElement {
label: qsTr("Save")
group: "save"
}
}

Component.onCompleted: {
container.getWidth(rowWidth);

if (container.extraWidth > 0) {
var addtlWidth = Math.floor(container.extraWidth/barModel.count)
for (var i = 0; i <= barModel.count - 1; i++) {
barRepeater.itemAt(i).width += addtlWidth ;
}
}
}

Repeater {
id: barRepeater

model: barModel

onItemAdded: {
barRow.rowWidth += item.width;
}

WizardBar {
id: wizardBar

property bool mid: index != 0
property bool curGroup: model.group === newConnPagesModel.curGroup // to synch with the wizard pages

onCurGroupChanged: {
if (curGroup) barRow.curIdx = index;
}

height: 60
width: label.paintedWidth + 10

active: index <= barRow.curIdx
position: index === 0 ? WizardBar.FirstLabel : ((index === (barModel.count - 1)) ? WizardBar.LastLabel : WizardBar.MiddleLabel) // labels are enum in class which indicates the shape of the arrow to be drawn

Item { // created as text container so that the arrow part will be excluded
id: labelItem

x: index != 0 ? parent.height/2 : 0
width: index != 0 ? parent.width - parent.height/2 : parent.width
height: parent.height

Text
{
id: label
anchors.centerIn: parent

color: wizardBar.active ? Colors.text.title.normal : Colors.text.title.disabled
font { pixelSize: Fonts.fontSize; family: Fonts.fontFamily }

text: model.label
}
}
}
}
}
}


Questions:
1. I connected the widthChanged() signal into update(). The label font pixelSize, scales based on screen diagonal width. However, seems like it is not updating when label.paintedWidth and screen orientation changes.
2. Is there a way to clear or delete previous render or force to clear all the delegate items of the repeater because they're not being cleared. I mean, I can still see the original render when I tried increasing the window size.
3. I'm still having a hard time figuring out how to move the model elements upon navigation and placing the dotted arrow if there is a missing element either on left or right or both. Can you give more tips please.
4. I tried putting a permanent dotted arrows on both sides of the main Item and control its visibility on missingWidth and adjust the width of those visible elements, however I still can get it to work, which goes back also to #3 problem in moving the elements. Is this even possible? Any tips?

Thank you!

wysota
30th April 2015, 11:24
Questions:
1. I connected the widthChanged() signal into update(). The label font pixelSize, scales based on screen diagonal width. However, seems like it is not updating when label.paintedWidth and screen orientation changes.
The diagonal length doesn't change when orientation changes. You should base your font on height value.


2. Is there a way to clear or delete previous render or force to clear all the delegate items of the repeater because they're not being cleared. I mean, I can still see the original render when I tried increasing the window size.
Repeater will recreate its items when the model is changed. However it seems to me you should just update your items in the situation you are describing.


3. I'm still having a hard time figuring out how to move the model elements upon navigation and placing the dotted arrow if there is a missing element either on left or right or both. Can you give more tips please.
I would have to sit down and try it myself. I don't know your exact use case, maybe your current approach doesn't fit it that well.


4. I tried putting a permanent dotted arrows on both sides of the main Item and control its visibility on missingWidth and adjust the width of those visible elements, however I still can get it to work, which goes back also to #3 problem in moving the elements. Is this even possible? Any tips?
What is it that you can't get to work? Hiding items?