Starting a second Timer after the first Timer's loop times out - qml

iam new to qml and iam trying to make 2 timers that change the same text label in a row,
i made the first one that refresh the text every 1 sec with a countdown of 10 sec and i want the second timer to start right after the first timer times out at zero with a new 5 sec from zero in the second timer ,
i used a ternary operator since i couldn't insert if statements without getting errors, it stops when it reaches zero of the first timer, i think that maybe the problem is that the running member just keeps on looping after it already hit zero,
any help!
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.1
Item {
id: root
width: 610
height: 548
property int counter1: 10
property int counter2: 5
ColumnLayout{
anchors.centerIn:parent
spacing: parent
Timer {
id:s1
interval: 1000
running: true
repeat: true
onTriggered: timeLabel.text = (0<counter1 ) ? counter1--:0
}
Timer {
id:s2
interval: 1000
running: true
repeat: true
onTriggered:timeLabel.text =(0<counter2 ) ? counter2--:0
}
Label{
id: timeLabel
color: "#FFFFFF"
font.pixelSize: 100
font.family: "Cherry"
}
}
}

The way you've defined your Timers, they will run in parallel at the same time, not sequentially. You need to set the second timer to not run until the first timer tells it to start.
Timer {
id:s1
interval: 1000
running: true
repeat: true
onTriggered: {
timeLabel.text = --counter1;
if (counter1 === 0) {
stop(); // Stop s1
s2.start(); // Start s2
}
}
}
Timer {
id:s2
interval: 1000
running: false // Don't start this yet
repeat: true
onTriggered: {
timeLabel.text = --counter2;
if (counter2 === 0) {
stop();
}
}
}

Related

QML progress bar is not updating smoothly

I want to update my progress bar every 5 ms to get smooth looking decrasing progress bar. I created timer and progres bar. Problem is that my progres bar looks like it is "jumping" from 100-80-60-40-20, nothing smooth.
import QtQuick
import QtQuick.Controls
ApplicationWindow {
id: root
visible: true
minimumWidth: 840
minimumHeight: 600
property real prgVal1: 100
Timer {
interval: 5
running: true
repeat: true
onTriggered: root.updateProgress()
}
function updateProgress() {
if (root.prgVal1 > 0)
root.prgVal1 -= 0.1
else
root.prgVal1 = 100
}
ProgressBar {
visible: true
width: 120
height: 40
x: 20
y: 50
value: root.prgVal1
from: 0
to: 100
}
}
Can anyone help me please?
Gif can be seen here: https://ibb.co/Wk4w2bn
This isn't an issue caused by your hardware but rather by the operating system. Because you didn't specify a specific style in your application it will pick up the native style of your OS, this is why it works on Ubuntu and not on Windows. The native Windows style of the QQuickProgressBar for some reason only updates in multiple of 5%. I couldn't find the related location in the code to share here.
You can work around the issue by using a different style by default like QtQuick.Controls.Universal.
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Universal
ApplicationWindow {
id: root
visible: true
width: 320
height: 240
Timer {
interval: 5
running: true
repeat: true
onTriggered: {
if (progressBar.value > 0)
progressBar.value -= 0.1
else
progressBar.value = 100
}
}
ProgressBar {
id: progressBar
anchors.centerIn: parent
width: 120
height: 80
from: 0
to: 100
}
}
Just use already existing components, instead of reinventing the things. Think declarative not imperative.
All you need is assign the value, nothing more. The Timer here is for example only.
ProgressBar {
id: progressBar
width: parent.width * 0.8
anchors.centerIn: parent
from: 0
to: 100
value: 0
Behavior on value {
PropertyAnimation { duration: 300; easing.type: Easing.OutBack }
}
}
Timer {
interval: 1000;
running: true;
repeat: true
onTriggered: {
var val = Math.floor(Math.random() * (progressBar.to - progressBar.from + 1) + progressBar.from);
progressBar.value = val;
}
}
[Edit: Original answers deleted]
#WITC okay, I have installed pyside6 (it appears to be based on Qt6.3.0) on Ubuntu 20 and used the following Python to start my application.
# main.py
import sys
from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
engine.quit.connect(app.quit)
engine.load('main.qml')
sys.exit(app.exec_())
Then I did small changes to your QML program so that I can capture all the image frames I'm seeing to keyframe PNGs.
// main.qml
import QtQuick
import QtQuick.Controls 6.3
ApplicationWindow {
id: root
visible: true
minimumWidth: 840
minimumHeight: 600
property real prgVal1:100
property int count: 0
Frame {
id: frame
background: Rectangle {
color: "white"
border.color: "black"
}
ProgressBar {
id: progressBar1
visible: true
implicitWidth: 120
implicitHeight: 40
value: prgVal1
from:0
to: 100
}
}
function step() {
frame.grabToImage(function (res) {
res.saveToFile(`/tmp/img/grab${count}.png`);
count++;
if (prgVal1 >= 1.0) {
prgVal1 -= 1.0;
Qt.callLater(step);
return;
}
} );
}
Component.onCompleted: step()
}
For combining the above image frames into an animated GIF, I used ffmpeg as follows:
# mkanim.sh
ffmpeg -y -framerate 20 -i '/tmp/img/grab%d.png' -vf fps=20,palettegen /tmp/img/pal.png
ffmpeg -y -framerate 20 -i '/tmp/img/grab%d.png' -i /tmp/img/pal.png -lavfi "fps=20 [x]; [x][1:v] paletteuse" anim.gif
You note that the animation I get is smooth.

What condition should i pass, to show the popup again

I implemented a feature where after pressing the back button twice then it will show a popup and that popup consist of tow buttons i.e yes and cancel, so when you press yes it closes the app.
My issue is when i click on cancel button then popup closes, and after that when i again press the back button it closes the app instead of showing the pop-up again. So what can I do? What condition should I pass?( It's in pure qml)
here is what i have done
Popup {
visible: false
id: popup
background: Rectangle {
width: 300
height: 200
}
Row{
topPadding: 10
Button {
text: qsTr("YES")
onClicked:
{
Qt.quit();
}
Button{
text: qsTr("cancel")
height:40
width:100
onClicked:
{
popup.close();
}
}
}
}
modal: true
focus: true
}
Keys.onBackPressed: {
timer.pressBack()
}
Timer{
id: timer
property bool backPressed: false
repeat: false
interval: 300//ms
onTriggered: backPressed = false
function pressBack(){
if(backPressed){
timer.stop()
backPressed = false
popup.open();
}
else{
backPressed = true
timer.start()
}
}
}
I guess this happens, because you're calling the close method directly and qml instead of closing the popup calls the close function of the main window instead (you didn't post the code for the leaveApp function so I can't tell for sure if this is what happens in your case). You also definitely don't need the call to leaveApp() after popup.open()
Here is the sample I used to test this. It should work as expected:
Keys.onReleased: {
if (event.key == Qt.Key_Back) {
event.accepted = true
timer.pressBack()
}
}
Timer{
id: timer
property bool backPressed: false
repeat: false
interval: 300//ms
onTriggered: backPressed = false
function pressBack(){
if(backPressed){
timer.stop()
backPressed = false
popup.open();
//leaveApp()
}
else{
backPressed = true
timer.start()
}
}
}
Popup {
id: popup
x: 100
y: 100
width: 200
height: 300
modal: true
focus: true
Row {
Button {
text: "ok"
onClicked: {
console.log("leave app")
mainWindow.close()
}
}
Button {
text: "cancel"
onClicked: {
console.log("close popup")
popup.close() //you have to call close method of popup explicitly (only close() will cause the issue)
}
}
}
}

Qml VideoOutput doesn't work after setting source to null once

import QtQuick 2.7
import QtQuick.Controls 2.0
import QtMultimedia 5.8
ApplicationWindow {
visible: true
width: 640
height: 480
property bool flip: true
Timer {
interval: 5000
running: true
repeat: true
onTriggered: {
flip = !flip
if(flip) {
videoOutput.source = null
} else {
videoOutput.source = player
}
}
}
VideoOutput {
id: videoOutput
anchors.fill: parent
source: player
}
MediaPlayer {
id: player
source: "file://video.mp4"
autoPlay: true
loops: MediaPlayer.Infinite
}
}
After a few a trigger of Timer that sets videoOutput.source to null, it will never work again. The VideoOutput will just have a stale painting of a previously decoded frame, and will never change.

Scheduling animations using a Timer in QML

I want to create a component that starts animations from a list. I've provided an example of what I want to do below. I don't know if this is the correct way in terms of performance and timer accuracy. Is it better to use a dedicated timer for each animation?
import QtQuick 2.0
Item {
id: root
function startOne() {
animOne.start()
}
function startTwo() {
animTwo.start()
}
property var listEvent: {
1000: root.startOne,
2000: root.startTwo
}
property int elapsed: 0
property int last_elapsed: 0
Rectangle {
id: one
x: 0
y: 0
width: 200
height: 200
color: "black"
}
Rectangle {
id: two
x: 600
y: 600
width: 200
height: 200
color: "black"
}
PropertyAnimation {
id: animOne
target: one
properties: "x"
to: 600
running: false
}
PropertyAnimation {
id: animTwo
target: two
properties: "x"
to: 0
running: false
}
function pop(last_elapsed, elapsed) {
for (var i = last_elapsed; i <= elapsed; i++) {
if (i in root.listEvent) {
listEvent[i]()
}
}
}
function scheduleEvent(delta) {
root.last_elapsed = root.elapsed
root.elapsed += delta
root.pop(root.last_elapsed, root.elapsed)
}
Timer {
id: scheduler
interval: 16
running: true
repeat: true
onTriggered: {
root.scheduleEvent(scheduler.interval)
console.log("timer: " + root.elapsed)
}
}
}

Why does this QML code have poor performance?

If QML rendering is hardware accelerated, shouldn't this simple example outperform the equivalent implementation in Qt classic?
import QtQuick 2.3
import QtQuick.Controls 1.2
ApplicationWindow {
id: app
visible: true
width: 640
height: 480
Column {
id: cc
property real val: 0
Row {
spacing: 10
TextField {
id: numRows
text: "1"
property int value: parseInt(text);
validator: IntValidator { bottom: 1; top: 100; }
}
TextField {
id: numCols
text: "1"
property int value: parseInt(text);
validator: IntValidator { bottom: 1; top: 100; }
}
}
Repeater {
model: numRows.value
Row {
Repeater {
model: numCols.value
Slider {
width: app.width / numCols.value
height: 18.5
value: cc.val
onValueChanged: cc.val = value
}
}
}
}
}
}
I idea is to fill the screen with rows and columns of sliders, and have each slider connect to every other slider. For my screen I use 55 rows and 20 columns. As I move a slider, I expect to see a fluid motion of all the sliders on the screen, yet the frame rate is very low (I would guess 5 to 10 fps). I have a very beefy GPU, and I was expecting much better performance. What could be wrong?
Looking at this under a (CPU, not QML) profiler, the majority of the time spent is actually in rendering the Slider items using native styling. If performance is an important issue, and native desktop styling is not, I would suggest not using QtQuickControls 1, as the styling has a significant amount of overhead.
If I update your example to use QtQuickControls 2, it is significantly smoother:
import QtQuick 2.3
import QtQuick.Controls 2.0
ApplicationWindow {
id: app
visible: true
width: 640
height: 480
Column {
id: cc
property real val: 0
Row {
spacing: 10
TextField {
id: numRows
text: "1"
property int value: parseInt(text);
validator: IntValidator { bottom: 1; top: 100; }
}
TextField {
id: numCols
text: "1"
property int value: parseInt(text);
validator: IntValidator { bottom: 1; top: 100; }
}
}
Repeater {
model: numRows.value
Row {
Repeater {
model: numCols.value
Slider {
width: app.width / numCols.value
height: 18.5
value: cc.val
onPositionChanged: cc.val = position
}
}
}
}
}
}
If you get yourself 1100 sliders, you are asking to continuously update 1100 sliders. That is a lot of signals being sent. I will not ask you what you need 1100 synchronized sliders for ...
Although you can inch out a few cycles by only updating cc.val when the slider is pressed, this will not change very much.
All in all, you can only seriously reduce the work being done by choosing a stepSize or setting updateValueWhileDragging: false. You will still see a delay when you release a slider, but the frame rate will not disturb the experience as much.