I'm creating a "dotted paper" component for my QML app. Essentially this components consists of a dotted background that the user can zoom upon. To create this I used the following code:
DottedPaper.qml
import QtQuick 2.15
Rectangle{
id: page
property int spacing: 50
// this property changes the spacing between the dots
property real contentScale: 1.0
Repeater{
id: rowRepeater
model: 200
Repeater{
id: row
property int rowIndex: index
model: 200
Rectangle{
id: circle
color: "red"
width: 4; height: 4
x: index * spacing * page.contentScale + width/2;
y: rowIndex * spacing * page.contentScale+ height/2
radius: width/2
}
}
}
}
Then in my main file I have the following:
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Flickable{
clip: true // doesn't improve anything
anchors.fill: parent
contentWidth: paper.width
contentHeight: paper.height
DottedPaper{
id: paper
width: 10000; height: 10000;
clip: true
}
}
// the slider changes the content scale of the dotted paper
Slider{
from: 1
to: 3
onValueChanged: paper.contentScale = value
}
}
The problem with this code is that it's slow. Upon checking the application does not render the content at 60fps, but I get around 45 - 50 fps on my desktop. I suspect the issues is that QML is reevaluating the contentScale property binding for each circle in the background even if the circle is not visible on the screen. What is the best way to prevent this?
It works a lot better if you change your Repeaters to be ListViews. That's because a ListView is smart enough to only create enough delegates as needed for the visible space. (At first I thought a GridView would work, but that doesn't give you enough control over rows/columns.)
Rectangle{
id: page
property int cellWidth: 50 * contentScale
property int dotWidth: 4
// this property changes the spacing between the dots
property real contentScale: 1.0
ListView {
id: grid
model: 200
anchors.fill: parent
anchors.leftMargin: page.dotWidth / 2
anchors.topMargin: page.dotWidth / 2
spacing: page.cellWidth - page.dotWidth
delegate: ListView {
model: 200
width: window.width
height: page.dotWidth
orientation: ListView.Horizontal
spacing: page.cellWidth - page.dotWidth
property int rowIndex: index
delegate: Rectangle{
id: circle
color: "red"
width: page.dotWidth; height: page.dotWidth
radius: width/2
}
}
}
}
Related
I am trying to change the height of rows in my TreeView based on the item depth in the tree. The itemDelegate has access to the depth property (so I can change height of the item), but the row height can only be set from the rowDelegate, which can't see item depth.
As you can see from example I need to change row heights to match depth. How can the rowdelegate determine the item depth from the model?
Component {
id: delegateMenuItem
Column {
height: 100 // No effect
spacing: 20 // No effect
bottomPadding: 20 // No effect
Rectangle {
border {
color: "blue"
width: 2
}
color: "red"
height: itemText.height * 2
width: 300
Text {
id: itemText
text: qsTr(styleData.value)+"-"+height
color: "white"
font.pointSize: fontSize(styleData.depth)
}
}
}
}
Using TreeView from QQC1, I suggest that you try setting the implicitHeight for the itemDelegate and not setting any height for the rowDelegate. So try changing your snippet to:
itemDelegate: Rectangle {
id: delegateMenuItem
border { color: "blue"; width: 2 }
color: "red"
width: ...
implicitHeight: styleData.depth * ...
Text {
id: itemText
anchors.fill: parent
...
}
}
(In your current implementation delegateMenuItem does not have any implicitHeight. Meaning its height is probably 0. Also the ColumnLayout as you are using it is useless so I have removed it.)
I have not tried the above. However I have tried doing what you want to do with TreeView from Qt marketplace. It is designed to work with QQC2 and is based on TableView from QQC2 so you can use rowHeightProvider.
Hmm.. I have 22 of these slider elements placed via a Repeater in my app.. Moving a slider is quite jitter-y and jerky, and that is on the nvidia gpu on my laptop. This was smoother on integrated graphics.. Is there anything plainly obvious I could improve? I'm fairly new to Qt, so this is a puzzle for me..
import QtQuick 2.0
import QtQuick.Controls 2.2
Item {
height: 4*82
width: 82
Rectangle {
height: 4*82
width: 83.55
color: "#0a0a0a"
border.color: "#80cccccc"
Rectangle {
width: 83.55
height: 22
color: "#0a0a0a"
border.color: "#80cccccc"
y:parent.y -22
Label {
x:parent.x+parent.width/2 - width/2
y:5
id: name
text: qsTr("MASTER EFX SP")
color: slider.value > 0 ? "#6eaf71" : "#3c3c3c"
font.pixelSize: 10
}
}
}
Slider {
id: slider
from: 0
to: 255
value: 0
orientation: Qt.Vertical
height: (4*82)-32
width: 83.55
y: 16
stepSize: 1.0
handle: Item {
y: parent.topPadding + parent.visualPosition * (parent.availableHeight - 50)
x: parent.leftPadding + parent.availableWidth / 2 - 30 / 2
Rectangle {
color: parent.parent.pressed || parent.parent.value > 0? "#901fdcf5" : "#90ffffff"
border.color: parent.parent.pressed || parent.parent.value > 0? "#1fdcf5" : "gray"
width: 30
height: 50
radius: 2
}
Rectangle {
color: parent.parent.pressed || parent.parent.value > 0? "#80000831" : "#4e4e4e"
width: 30
height: 2
y: 50/2-2
}
}
background: Rectangle {
x: parent.leftPadding + (horizontal ? 0 : (parent.availableWidth - width) / 2)
y: parent.topPadding + (horizontal ? (parent.availableHeight - height) / 2 : 0)
implicitWidth: horizontal ? 200 : 6
implicitHeight: horizontal ? 6 : 200
width: horizontal ? parent.availableWidth : implicitWidth
height: horizontal ? implicitHeight : parent.availableHeight
radius: 0
border.color: "#3c3c3c"
color: "#3c3c3c"
scale: horizontal && parent.mirrored ? -1 : 1
readonly property bool horizontal: parent.orientation === Qt.Horizontal
}
}
}
First, try to understand what's wrong - it might not be the sliders after all.
use Gammaray to open your Qml application and examine the graphics stack.
From the code that you pasted, the only thing I could think that migth cause an issue is the radius property, as it's the only expensive thing your code is doing.
Also, try to update the library imports to the latest qt version, you are using a really old one and new ones also migth improve performance.
I'll begin with my testcase. It's pretty basic.
It creates a rectangle rotating in 3D, and a DropShadow element for the rectangle.
It animates the radius property of the DropShadow.
It creates a 1x1px Canvas3D repainted constantly, so I can check how often it manages to get repainted with all the other stuff going on (Canvas3D has a built-in fps property). I use that for measuring the app's framerate.
When the value of radius is animated, the app is running at 10FPS. When this value is not animated, the app is running at 60FPS.
The point of the 3D rotation is to have some animation in the rectWrapper element, so the shadow has to get constantly recalculated, even if its radius is not changed. This proves that calculating the shadow is very fast, and what's slowing it down is just the constant changing of the radius.
My code:
main.cpp: (the only unusual thing in it is AA_UseOpenGLES, which is necessary because of my sub par OpenGL drivers)
#include <QGuiApplication>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
QGuiApplication::setAttribute(Qt::AA_UseOpenGLES);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
main.qml:
import QtQuick 2.5
import QtQuick.Window 2.2
import QtGraphicalEffects 1.0
import QtCanvas3D 1.1
Window {
visible: true
width: 640
height: 480
Text {
text: canvas3d.fps + " FPS"
font.pointSize: 18
}
// `rectWrapper` is needed as a layer of indirection around
// `rect` so DropShadow is aware of `rect`'s the rotation
Item {
id: rectWrapper
width: rect.width
// +100 here to accomodate the extra space needed due to
// 3D perspective projection of the 3D-rotated inner item
height: rect.height + 100
anchors.centerIn: parent
visible: false
Rectangle {
id: rect
color: "red"
width: 300
height: 100
anchors.centerIn: parent
property real yRot: 50
transform: Rotation {
origin.x: rect.width/2;
origin.y: rect.height/2;
axis { x: 0; y: 1; z: 0 }
angle: rect.yRot
}
SequentialAnimation on yRot {
loops: Animation.Infinite
NumberAnimation {
from: -180; to: 180; duration: 5000
}
}
}
}
DropShadow {
source: rectWrapper
anchors.fill: rectWrapper
samples: 16
radius: 16
SequentialAnimation on radius {
//running: false
loops: Animation.Infinite
NumberAnimation {
from: 2; to: 16; duration: 1000
}
}
}
Canvas3D {
id: canvas3d
width: 1; height: 1 // nonzero size so it can be redrawn
property var gl;
onInitializeGL: {
// should get and save context, otherwise FPS isn't measured for some reason
gl = canvas3d.getContext("canvas3d", {depth:true, antialias:true, alpha:true});
}
}
}
Uncomment the running: false to see the huge speedup.
My theory: The blur shader used by DropShadow may be getting recompiled on every change of radius. I've heard of that happening when you upload new values of bool uniforms in GLSL, but in this case it's not a bool, it's a real.
The question: Why does this slowdown occur? Can I work around it?
What I am attempting to do is anchor an Item over a particular place over an Image. The Image needs to use Image.PreserveAspectFit for the fillMode. The problem I am having is that the height/width/paintedWidth/paintedHeight of the Image are for the entire image "canvas" (I'm not sure what it's actually called), not the drawn part of the image itself. So I can't anchor to the actual image.
See code below for an example I tried with anchors (red rectangle) and with a child rectangle and x,y coordinates (green rectangle).
import QtQuick 2.3
import QtQuick.Window 2.2
Window {
visible: true
height: 400
width: 400
Image {
id: image
anchors.fill: parent
fillMode: Image.PreserveAspectFit
source: "image.jpg"
Rectangle {
id: bottomRight
width: 40
height: 40
color: "green"
x: parent.width * 0.75
y: parent.height * 0.75
}
}
Rectangle {
id: topLeft
width: 40
height: 40
color: "red"
anchors.top: image.top
anchors.left: image.left
anchors.topMargin: image.height * 0.25
anchors.leftMargin: image.width * 0.25
}
}
When you change the size of the window the placement of the rectangles is not in the same place over the image. I'd post some screenshots but I don't have enough reputation yet!
I poked around through the widget tree using the debugger but I can't seem to find any properties that provide the information about the scaling that I could use to calculate placement of the rectangles.
Update
I used the following functions to calculate the margins since I will be using potentially lots of these overlays.
function horMargin(val)
{
return ((image.width - image.paintedWidth)/2 + image.paintedWidth * val)
}
function verMargin(val)
{
return ((image.height - image.paintedHeight)/2 + image.paintedHeight * val)
}
Did you try to use paintedWidth and paintedHeight? :)
This will give you a rectangle that fills the painted image:
import QtQuick 2.3
import QtQuick.Window 2.2
Window {
visible: true
height: 400
width: 400
Image {
id: image
anchors.fill: parent
fillMode: Image.PreserveAspectFit
source: "image.jpg"
}
Rectangle {
x: (image.width - image.paintedWidth) / 2
y: (image.height - image.paintedHeight) / 2
width: image.paintedWidth
height: image.paintedHeight
color: "transparent"
border.color: "darkorange"
border.width: 2
}
}
From there, you can work out how to apply the margins that you need.
I am trying to implement an image viewer (sort of):
model/view approach
using both c++ and qml
the images are just a ListView filled up with Image (delegate) items
Here is a piece of code:
property double zoomFactor: 1.5;
property double imgScale: 1;
CustomToolBar
{
id: toolBar
onZoomInSignal:
{
imgScale = imgScale*zoomFactor;
}
onZoomOutSignal:
{
imgScale = imgScale/zoomFactor;
}
onZoomFitSignal:
{
imgScale = 1;
}
}
Rectangle
{
id: bgRect
Layout.fillWidth: true
Layout.fillHeight: true
color: "Grey"
anchors.margins: 10
ScrollView
{
id: scrollView
anchors.fill: parent
ListView
{
id: listView
anchors.fill: parent
clip: true
spacing: 10
model: listItemModel
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: 10
boundsBehavior: Flickable.StopAtBounds
delegate: Image
{
id: item_id
anchors.horizontalCenter: parent.horizontalCenter
source: item_img_path + "/" + Math.random()
scale: imgScale
}
}
}
}
The images are loaded correctly, I need to be able to zoom them.
In order to zoom I just modify the scale property of the Image delegate.
Images not scaled (scale = 1) CORRECT:
Images unzoomed (scale = 1/1.5) WRONG! images spacing (?) is being incremented:
Images zoomed (scale = 1.5) WRONG! images overlap:
As you can see, zoom minus increments the spacing (I'm quite not sure it is really the spacing) between the images, and zoom plus overlaps the images.
Furthermore, it would be nice to have the horizontal scrollbar for the ScrollView when zooming in, but I cannot achieve this.
Can anyone help me?
Thanks
EDIT:
Following the solution proposed by grillvott the images are zoomed in/out correctly, but the whole ListView is getting smaller/bigger with them:
The result should be instead something like this (gimp mode ON):
Any idea?
I don't think that scale will take any boundaries into consideration. You could encapsulate the image and use fillMode to make sure the image scales accordingly:
delegate: Item {
anchors.horizontalCenter: parent.horizontalCenter
width: img.sourceSize.width * imgScale
height: img.sourceSize.height * imgScale
Image {
id: img
anchors.fill: parent
source: item_img_path + "/" + Math.random()
fillMode: Image.Stretch
}
}