Scheduling animations using a Timer in QML - 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)
}
}
}

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.

ScaleAnimator not working inside SequentialAnimation

Following code gives the error:
"QML ScaleAnimator: setRunning() cannot be used on non-root animation nodes."
It works fine with Qt5 though. Cant we call animators inside Sequential animations:
import QtQuick
Window {
id: root
width: 640
height: 480
visible: true
Rectangle {
id: c1
color: "skyblue"
}
SequentialAnimation {
running: true
loops: Animation.Infinite
ScaleAnimator {
target: c1
from: 1
to: 0.5
duration: 2000
running: true
}
ScaleAnimator {
target: c1
from: 0.5
to: 1
duration: 2000
running: true
}
}
}

QML AreaSeries with SplineSeries

I am checking the docs of AreaSeries, especialy its upperSeries property and it is obvious it accepts only LineSeries object. However, I want to make AreaSeries to be constructed with SplineSeries and if I do it:
ChartView
{
Layout.fillWidth: true
Layout.fillHeight: true
antialiasing: true
titleColor: "steelblue"
title: qsTr("Sensor data")
theme: ChartView.ChartThemeDark
animationOptions: ChartView.AllAnimations
dropShadowEnabled: true
AreaSeries
{
name: qsTr("Air pressure [kPa]")
color: "green"
borderColor: "white"
borderWidth: 4
brushFilename: "qrc:/icons/UeGradient.svg"
pointLabelsVisible: true
axisX: DateTimeAxis
{
id: ueDateTimeAxisAirPressure
format: "dd.MM.yyyy"
min: ueSensorsData.ueGetMinimalDate()
max: ueSensorsData.ueGetMaximalDate()
} // DateTimeAxis
axisY: ValueAxis
{
id: ueAirpressureAxis
min: ueSensorsData.ueGetMinimalAirPressure()
max: ueSensorsData.ueGetMaximalAirPressure()
} // axisY
upperSeries: SplineSeries
{
id: ueLineSeriesAirPressure
Connections
{
target: ueSensorsData
onUeSignalUpdateGraph:
{
var valueX=ueSensorsData.ueGetLastTimeStamp();
var valueY=ueSensorsData.ueGetLastAirPressure();
ueLineSeriesAirPressure.append(valueX,
valueY);
ueDateTimeAxisAirPressure.min=ueSensorsData.ueGetMinimalDate();
ueDateTimeAxisAirPressure.max=ueSensorsData.ueGetMaximalDate();
ueAirpressureAxis.min=ueSensorsData.ueGetMinimalAirPressure();
ueAirpressureAxis.max=ueSensorsData.ueGetMaximalAirPressure();
} // onUeSignalUpdateGraph
} // Connections
} // upperSeries
} // AreaSeries
} // ChartView
I get following error:
QQmlApplicationEngine failed to load component
qrc:/main.qml:304 Cannot assign object to property
and the error points to problematic line of code upperSeries: SplineSeries.
Is it aught possible to acomplish this task using some other approach, I haven't found it on net.

QML How reverse play animation

I want an object, need it to fellow a complex path and move as an animation.
The path is included line and curve. just like a train.
Two solution: 1. PathAnimation or 2. states with multi-animation
Problem of solution 1:
The train maybe stop at a random time-point(pause the animation), and go reverse back to the start position(play animation reversely).
So i want know any way to play PathAnimation reversely?
I think QML doesn't have that functionality.
You could set a new path whenever you need a new one. I.e, when you animation ends.
Here you have an example:
import QtQuick 2.3
import QtQuick.Controls 1.1
ApplicationWindow {
visible: true
width: 350
height: 300
Rectangle {
id: window
width: 350
height: 300
Canvas {
id: canvas
anchors.fill: parent
antialiasing: true
onPaint: {
var context = canvas.getContext("2d")
context.clearRect(0, 0, width, height)
context.strokeStyle = "black"
context.path = pathAnim.path
context.stroke()
}
}
SequentialAnimation {
id: mySeqAnim
running: true
PauseAnimation { duration: 1000 }
PathAnimation {
id: pathAnim
duration: 2000
easing.type: Easing.InQuad
target: box
orientation: PathAnimation.RightFirst
anchorPoint: Qt.point(box.width/2, box.height/2)
path: myPath1
}
onStopped: {
console.log("test")
pathAnim.path = myPath2;
mySeqAnim.start();
}
}
Path {
id: myPath1
startX: 50; startY: 100
PathLine { x: 300; y: 100 }
onChanged: canvas.requestPaint()
}
Path {
id: myPath2
startX: 300; startY: 100
PathLine { x: 50; y: 100 }
onChanged: canvas.requestPaint()
}
Rectangle {
id: box
x: 25; y: 75
width: 50; height: 50
border.width: 1
antialiasing: true
Text {
anchors.centerIn: parent
}
}
}
}

Using a slider under another mouse area QML / QT

I am designing an UI interface in Qt where one of the pages can swipe between views. I have defined a SwipeArea region in the whole screen (which is a MouseArea I defined in another QML file), to be able to swipe between pages, with the property propagateComposedEvents set to true so that I can swipe between pages, and still being able to click on all buttons.
The problem comes when using a Slider. Apparently it can't get these mouse events, and I can't interact with it because when I click on the screen it starts the swiping handler. Any idea of how can I solve this?
This is where I define the SwipeArea.qml code:
import QtQuick 2.0
MouseArea {
property point origin
property bool ready: false
signal move(int x, int y)
signal swipe(string direction)
propagateComposedEvents: true
onPressed: {
drag.axis = Drag.XAndYAxis
origin = Qt.point(mouse.x, mouse.y)
}
onPositionChanged: {
switch (drag.axis) {
case Drag.XAndYAxis:
if (Math.abs(mouse.x - origin.x) > 16) {
drag.axis = Drag.XAxis
}
else if (Math.abs(mouse.y - origin.y) > 16) {
drag.axis = Drag.YAxis
}
break
case Drag.XAxis:
move(mouse.x - origin.x, 0)
break
case Drag.YAxis:
move(0, mouse.y - origin.y)
break
}
}
onReleased: {
switch (drag.axis) {
case Drag.XAndYAxis:
canceled(mouse)
break
case Drag.XAxis:
swipe(mouse.x - origin.x < 0 ? "left" : "right")
break
case Drag.YAxis:
swipe(mouse.y - origin.y < 0 ? "up" : "down")
break
}
}
}
And here is the main.qml where I use it:
Item {
id: content
width: root.width * itemDataLength
height:parent.height
property double k: (content.width - root.width) / (img.width - root.width)
Repeater {
id:repeater
model: itemDataLength
width:parent.width
height:parent.height
Loader{
id:loader
width:parent.width / 2
x: root.width * index
height:parent.height
source:loaderItems[index]
}
}
}
SwipeArea {
id: mouse
anchors.fill: parent
onMove: {
content.x = (-root.width * currentIndex) + x
}
onSwipe: {
switch (direction) {
case "left":
if (currentIndex === itemDataLength - 1) {
currentIndexChanged()
}
else {
currentIndex++
}
break
case "right":
if (currentIndex === 0) {
currentIndexChanged()
}
else {
currentIndex--
}
break
}
}
onCanceled: {
currentIndexChanged()
}
}
Maybe important to add, my slider code:
Slider {
id: slider
anchors.centerIn: parent
updateValueWhileDragging:true
activeFocusOnPress:true
style: SliderStyle {
id:sliderStyle
//Slider handler
handle: Rectangle {
id:handler
width: 22
height: 44
radius:2
antialiasing: true
Image{
source:"icons/slidercenter.png"
anchors.centerIn: parent
}
}
//Slider Groove
groove: Item {
id:slidergroove
implicitHeight: 50
implicitWidth: temperaturePage.width -50
}
}
}
I appreciate any help or opinion, and thank you in advance!