QML Canvas improperly rendered with QT_SCALE_FACTOR set - qt

I have a Qt/QML program that runs on Ubuntu and on a Raspberry Pi 3B+. The program runs fine on both platforms however the Pi has a high DPI screen so I set a QT_SCALE_FACTOR to 1.3 on that device to make controls appear the right size. It works perfectly except for one thing: indicators on ComboBox and SpinBox controls are drawn the same size as if no QT_SCALE_FACTOR was set, and the rest of the rectangle they would fill if the QT_SCALE_FACTOR was applied is filled with random pixels. Setting a QT_SCALE_FACTOR on Ubuntu works without any issues.
I tried replacing the indicators with a custom one but I get the same result.
Leaving QT_SCALE_FACTOR unset produces correctly rendered indicators.
This is with Qt 5.13.2 and QtQuick.Controls 2.5 on Linux pi3 4.19.75-v7+ #1270 SMP Tue Sep 24 18:45:11 BST 2019 armv7l GNU/Linux
The pictures below illustrate the behavior with QT_SCALE_FACTOR=2:
This is the code for the custom indicator in the first picture:
ComboBox {
id: cb
...
width: 200
...
indicator: Canvas {
id: canvas
x: cb.width - width - cb.rightPadding
y: cb.topPadding + (cb.availableHeight - height) / 2
width: 12*2
height: 8*2
contextType: "2d"
Connections {
target: cb
function onPressedChanged() { canvas.requestPaint(); }
}
onPaint: {
var context = getContext("2d");
context.reset();
context.beginPath();
context.moveTo(0, 0);
context.lineTo(width, 0);
context.lineTo(width / 2, height);
context.closePath();
context.fillStyle = cb.pressed ? "#17a81a" : "#21be2b";
context.fill();
}
}

Related

qml problem to resizeable scene3d in layout

In order to make a resizable 3d program i have created a scene3d in a layout and set the required properties. The scene resizing works properly while i change the height of windows but not in case of changing width.
I checked two examples of QTquick 3D and find out there's the same problem there.
To solve that i tried resizing windows by dynamic change scene scale in transform matrix4*4, and got this error:
after i ignore this, the program works correctly and resizes scene
AirplaneScene.qml:
Transform {
id: toyplaneTransform
matrix: {
var m = Qt.matrix4x4();
m.translate(Qt.vector3d(-30, -15, -30));
m.rotate(angle, Qt.vector3d(0, 1, 0));
m.rotate(rollAngle, Qt.vector3d(1, 0, 0));
m.rotate(pitchAngle, Qt.vector3d(0, 0,1));
m.scale(1.0 /scaleFactor );
return m;
}
main.qml:
Item{
id:sceneItem
Layout.preferredHeight: 700
Layout.preferredWidth: 1200
Layout.fillWidth: true
Layout.fillHeight: true
AirplaneScene{
id:airplane
scaleFactor: Math.round(mainLayout.width/sceneItem.width * 10) / 10
}
}
(when manually set the scalafactor this error not displaying)
I solved this problem by initialization scalefaktor with 3d scene to mainLayout ratio in AirplaneScene qml, and after loading page i changed scalefaktor dynamically
main.qml
property bool completeLoad: false
Component.onCompleted: {
completeLoad = true
}
onWidthChanged: {
if(completeLoad)
plane.scaleFactor= Math.round(mainLayout.width/sceneItem.width * 10) / 10
}
initialization scalefaktor in AirplaneScene.qml:
property real scaleFactor:1.3

Weird canvas behavior in QT/QML

I have a strange issue in QML when trying to draw relatively simple graphics.
I'm using PyQt 5 with QT 5.11 and Ubuntu 18.04.
Since the graphics part I'm writing is mostly stationnary (changes about each 1 secs), I found out canvas would be a convenient way to draw graphics without using QPainter. But it's been noting but a nightmare with fonts.
For example, when drawing this simple QML component:
import QtQuick 2.11
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.11
import QtGraphicalEffects 1.0
Rectangle {
id: cont_rect
Button {
text: "Repaint"
onClicked: {
cv.clearcv()
cv.requestPaint()
}
}
property string cvfont: "13px Ubuntu"
height: 80
width: 300
color: "#132931"
Canvas {
anchors.fill: parent
antialiasing: true
contextType: '2d'
id: cv
width: cont_rect.width
height: cont_rect.height
function clearcv() {
var ctx = cv.context
ctx.clearRect(0, 0, width, height)
ctx.fillStyle = "black"
}
function drawStateLine() {
var ctx = (cv.context == null) ? getContext("2d", {alpha: false}) : cv.context
var x = width/2, y = height - 10
var txt_angle = -45 * 2*Math.PI / 360
ctx.save()
ctx.translate(x,y)
ctx.rotate(txt_angle)
ctx.fillStyle = "royalblue"
ctx.textAlign = "left"
ctx.font = cvfont
ctx.textBaseline = "middle"
ctx.globalAlpha = 1
ctx.fillText("Detected", 0, 0)
ctx.restore()
}
onPaint: {
drawStateLine();
}
}
}
I end up with extremely weird graphics behavior.
The FIRST time my component is rendered, I have good looking text:
Then, when I click my specially crafted button (for the sake of isolating the problem down to a simple component), I have:
Basically, whenever I am trying to repaint my damn canvas, first clearing it (I also tried ctx.reset(), ctx.fillRect with my background color, etc.), I end up with font rendering that is much less readable and that bothers me.
Is there anyone here who has an idea on how to avoid this?
I don't know if it's useful, but I use a 27in display with 1080p resolution.
Thanks!

How to make the Window to be as high as the WebEngineView when the latter's size is not constrained and natural

I use Qt 5.10.1 on up-to-date Windows 10 and the following simple program does not show any window:
import QtQuick 2.10
import QtQuick.Window 2.10
import QtWebEngine 1.5
Window { id: w
visible: true
title: "Test"
// with this line, the program crashes before showing anything:
height: v.contentsSize.height
WebEngineView { id: v
anchors.left: w.left
anchors.right: w.right
anchors.top: w.top
onContentsSizeChanged: {
console.log(contentsSize) // no output if not both width and height properties of the web view are specified
w.height = contentsSize.height
}
// if any of the following 2 lines are omitted, the web view the ":-)" string in the web view does not show up and the window looks empty although anchors.left and anchors.right are set above and the height is set
// width: 100
// height: 100
// The following line crashes the program before showing the window
// height: v.contentsSize.height
Component.onCompleted: {
loadHtml(":-)");
}
}
}
I specifically want the window to be as high as the web view when its size is not constrained. Relevant documentation link: http://doc.qt.io/qt-5/qml-qtwebengine-webengineview.html#contentsSize-prop.
// with this line, the program crashes before showing anything:
height: v.contentsSize.height
That is because contentsSize is undefined at this point .. you haven't loaded any content yet, the qml engine crashes with undefined height. So just leave Window with no height .. equivalent to (height:0)
I specifically want the window to be as high as the web view when its
size is not constrained.
Then don't anchor the WebEngineView .. that's the problem, because your anchoring - which is not complete - will give WebEngineView an initial height following initial default height of Window, but you never change it later after loading .. so even Window will not be able to resize to a smaller height!
To do that safely with minimum change, set height/width of WebEngineView to any value initially .. then once the contents are loaded, reset WebEngineView height / width to contentsSize .. so thatWindow can resize to that height.
Window { id: w
visible: true
title: "Test"
WebEngineView {
id: v
width:1
height:1
onContentsSizeChanged: {
console.log(contentsSize) // no output if not both width and height properties of the web view are specified
//
// here you can set web view height so that Window heigth is same as webview
width = contentsSize.width
height = contentsSize.height
// and Window height
w.height = contentsSize.height
}
Component.onCompleted: {
loadHtml(":-)");
}
}
}

How to use QML Scale Element for incremental scaling with different origin

I'm trying to use a QML Scale Element to perform view scaling around a point clicked by the user, but it's not always working as documented.
To reproduce the problem, run the minimal QML example below (I'm using Qt 5.3.1 on Ubuntu 14.04 x86_64) and then:
Click in the center of the blue rectangle at the top left.
See that everything is scaled up, but the center of the blue rectangle remains at your click location. This is as documented in http://doc.qt.io/qt-5/qml-qtquick-scale.html - "[The origin] holds the point that the item is scaled from (that is, the point that stays fixed relative to the parent as the rest of the item grows)."
Now click in the center of the red rectangle.
See that everything is scaled up, but the center of the red rectangle did not remain at your click point, it was translated up and to the left. This is not as documented.
My goal is to have it always zoom correctly maintaining the click point as the origin, as stated in the documentation.
P.S. Interestingly, if you now click again in the center of the red rectangle, it scales up around that point as promised. Clicking again now on the center of the blue rectangle, you see the same unexpected translation behaviour.
P.P.S. I'm working on an application where the user can mouse-wheel / pinch anywhere on the containing rectangle, and everything inside should scale up or down around the mouse / pinch position. Many applications have exactly this behaviour. See for example inkscape.
import QtQuick 2.2
import QtQuick.Controls 1.1
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Rectangle {
x: 100
y: 100
width: 300
height: 300
transform: Scale {
id: tform
}
MouseArea {
anchors.fill: parent
onClicked: {
console.log(mouse.x + " " + mouse.y)
tform.xScale += 0.5
tform.yScale += 0.5
tform.origin.x = mouse.x
tform.origin.y = mouse.y
}
}
Rectangle {
x: 50
y: 50
width: 50
height: 50
color: "blue"
}
Rectangle {
x: 100
y: 100
width: 50
height: 50
color: "red"
}
}
}
(I filed this as a Qt bug, because the behaviour does not follow the documentation. At the moment of writing, the bug seems to have been triaged as "important". https://bugreports.qt.io/browse/QTBUG-40005 - I'm still very much open to suggestions of work-arounds / fixes over here)
In fact it is not a deviant behavior, just a different one from what you may expect after reading the doc.
When you change the Scale transform of your rectangle, the transformation is applied on the original rectangle. The point you click on stay in the same place from the original rectangle point of view.
That's why your rectangle "moves" so much when you click on one corner then the opposite corner.
In order to achieve what you want, you can't rely on transform origin. You have to set the actual x y coordinates of your rectangle.
Here's a working example:
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Rectangle {
id: rect
x: 100
y: 100
width: 300
height: 300
Rectangle {
x: 50
y: 50
width: 50
height: 50
color: "blue"
}
Rectangle {
x: 100
y: 100
width: 50
height: 50
color: "red"
}
transform: Scale {
id: tform
}
MouseArea {
anchors.fill: parent
property double factor: 2.0
onWheel:
{
if(wheel.angleDelta.y > 0) // zoom in
var zoomFactor = factor
else // zoom out
zoomFactor = 1/factor
var realX = wheel.x * tform.xScale
var realY = wheel.y * tform.yScale
rect.x += (1-zoomFactor)*realX
rect.y += (1-zoomFactor)*realY
tform.xScale *=zoomFactor
tform.yScale *=zoomFactor
}
}
}
}

QML NumberAnimation.duration behaviour

I'm trying to make a player move smoothly towards a destination in QML. I'm using a NumberAnimation to animate the x,y position changes. The NumberAnimation's duration should be proportional to the distance the player has to travel, so that the player moves at the same speed regardless of how far away they are from their destination.
import QtQuick 1.1
Item {
width: 720
height: 720
MouseArea {
anchors.fill: parent
onClicked: {
var newXDest = mouse.x - player.width / 2;
var newYDest = mouse.y - player.height / 2;
var dist = player.distanceFrom(newXDest, newYDest);
// Make duration proportional to distance.
player.xMovementDuration = dist; // 1 msec per pixel
player.yMovementDuration = dist; // 1 msec per pixel
console.log("dist = " + dist);
player.xDest = newXDest;
player.yDest = newYDest;
}
}
Rectangle {
id: player
x: xDest
y: yDest
width: 32
height: 32
color: "blue"
property int xDest: 0
property int yDest: 0
property int xMovementDuration: 0
property int yMovementDuration: 0
function distanceFrom(x, y) {
var xDist = x - player.x;
var yDist = y - player.y;
return Math.sqrt(xDist * xDist + yDist * yDist);
}
Behavior on x {
NumberAnimation {
duration: player.xMovementDuration
// duration: 1000
}
}
Behavior on y {
NumberAnimation {
duration: player.yMovementDuration
// duration: 1000
}
}
}
Rectangle {
x: player.xDest
y: player.yDest
width: player.width
height: player.height
color: "transparent"
border.color: "red"
}
}
My problem can be demonstrated by running the application above and following these steps:
Click on the bottom right hand corner of the screen.
Immediately click in the centre (or closer towards the top left) of the screen.
On the second click (while the rectangle is still moving), it seems that the rectangle's number animation is stopped (which is what I want) but it assumes the position of the destination (not what I want). Instead, I want the animation to stop and the rectangle to assume the position at which it was stopped, then to continue on to the new destination.
The correct behaviour - ignoring that the movement speed becomes disproportional - can be seen by setting both of the NumberAnimation.durations to 1000.
I think that you are looking for SmoothedAnimation. There are only two types of animation that deal nicely with the destination changing before the animation is completed. That is SmoothedAnimation and SpringAnimation. Both of these use the current position and velocity to determine the position in the next frame. Other animation types move the position along a predetermined curve.
Simply changing NumberAnimation to SmoothedAnimation makes your example look correct to me.

Resources