QML NumberAnimation.duration behaviour - qt

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.

Related

QT-QML: How to create image (line) and work on it

I need to create and move an image on Qt but i cannot understand how to do that.
The image is just a line like the following:
The result that I'd like to obtain is to have a line which I can shift right or left (it should continue even outside the canvas for some cm) and color the line(or glowing it white with another background) when I call a function.
To do that im using a qml file in qt and I'm creating this line by using the Canvas object, but it is far away from the image showed before:
Canvas {
id: mycanvas
width: 1000; height: 600
contextType: "2d"
property var l_mez: 273
property var pp: 245
Path {
id: myPath
startX: 100; startY: 300
PathCurve { x: (100+(mycanvas.l_mez/2)); y: (300+mycanvas.pp/2) }
PathCurve { x: 100+(mycanvas.l_mez*3/2)-40; y: 300-mycanvas.pp/2+15 }
PathCurve { x: 100+(mycanvas.l_mez*3/2)+40; y: 300-mycanvas.pp/2+15 }
PathCurve { x: 100+(mycanvas.l_mez*2); y: 300}
PathCurve { x: 100+(mycanvas.l_mez*5/2); y: 300+mycanvas.pp/2 }
PathCurve { x: 100+(mycanvas.l_mez*7/2); y: 300-mycanvas.pp/2 }
}
onPaint: {
var ctx = getContext("2d");
ctx.strokeStyle = "grey";
ctx.path = myPath;
ctx.lineWidth =9
ctx.stroke();
}
The result is :
The question is, how can i properly draw the line on the first image?
I was thinking I could just create the image and import it but then I could not shift it and change the color of the line as I prefer.
Thanks for the help
PathCurve is really overspecifying the curve in your example which is where the waviness is coming from. I would instead use alternating PathLine and PathArc to achieve that look.
Note, you could do this with an image also if you were willing to give up on changing the line color. You can load your sample image above twice and place them next to each other so that they seamless connect. You would place them both inside of one Item and turn on clipping.
Then you translate them both left or right to give the sense of movement. When you get to the far end of the images in either direction you just jump the translation back by the image width and keep going.
It will look seamless and smooth to the user. Especially if you use an XAnimator as those run in a separate thread.

QML Animation: How to stop infinite animation in exact position?

I'm trying to stop infinite animation from function where position is known.
I need animation to animate until the x = pos and then to stop.
NumberAnimation on x {
id : animationObject
loops: Animation.Infinite
from: 0
to: 500
running: true
duration: 3000
}
I have tried to stop it in this way, but it doesn't help.
function stop(pos) {
animationObject.loops = 1;
animationObject.to = pos;
}
You can set an expression as value of the property running to be true if the position is not reached and false otherwise.
The x property is also associated to the signal onXChanged that will be triggered each time the item moved. You can also use it if you don't want/can't change the animation definition:
Rectangle {
anchors.fill: parent
Rectangle {
id: rect
width: 100
height: 100
color: "red"
property int bound: 50
NumberAnimation on x {
id : animationObject
loops: Animation.Infinite
from: 0
to: 100
running: Math.abs(rect.x - bound) > 1
duration: 3000
}
onXChanged: console.log(rect.x, Math.abs(rect.x - bound) > 1)
}
}
In the code above, the animation will be stopped when the rectangle reachs the X coordinate defined by the property bound.
As the value will be a floated number, we can't use the equality and have to use a margin error (1 in my code).

Dragging the range of a RangeSlider

In my Qt project for viewing logfiles with charts, I need a range slider for easy navigation on the time axis.
What I need is support for dragging both handles; when clicking and dragingg in between the handles, both handles are moved together. The behaviour of the RangeSlider is to position the nearest handle to the clicked position, and I cant find any way of changing this from any properties.
When the provided controls is not providing the required functionality, I wonder if the only approach is to create a new component from scratch, or if there is any way of customizing the existing controls?
What you want is certainly possible:
RangeSlider {
id: sli
width: 300
from: 0
to: 1000
first.value: 300
second.value: 700
Rectangle {
anchors.left: sli.first.handle.right
anchors.right: sli.second.handle.left
anchors.verticalCenter: sli.verticalCenter
height: sli.first.handle.height
color: "red"
MouseArea {
anchors.fill: parent
property real dx: 0
onPressed: dx = mouseX
onPositionChanged: {
var d = ((mouseX - dx) / sli.width) * Math.abs(sli.to - sli.from)
if ((d + sli.first.value) < sli.from) d = sli.from - sli.first.value
if ((d + sli.second.value) > sli.to) d = sli.to - sli.second.value
sli.first.value += d
sli.second.value += d
dx = mouseX
}
}
}
}
The red rectangle is actually redundant, I merely put it there as a visual aid.

ListView scrolling animation

I want to implement a scrolling animation for QML ListView. Here is a sample image:
Can anybody advise me for implementing this?
Thank you.
The ViewTransition provides a lot of interesting examples on how to animate a ListView for operations like populate (the transition for the initial items at component creation), add, remove (self-explanatory) as well as other operations.
Given a ListView you define an element Transition for each operation you want to animate. The animation framework can be exploited to create compound animations, by simply combining the basic animations to create the (more or less) complex behaviour you are interested in (see also here for an actual example).
Here a definition for a ListView (the first linked document provides some nice images):
ListView {
// data model, delegate, other usual stuff here...
// transitions for insertion/deletation of elements
add: Transition {
NumberAnimation { property: "opacity"; from: 0; to: 1.0; duration: 500 }
NumberAnimation { property: "scale"; easing.type: Easing.OutBounce; from: 0; to: 1.0; duration: 750 }
}
addDisplaced: Transition {
NumberAnimation { properties: "y"; duration: 600; easing.type: Easing.InBack }
}
remove: Transition {
NumberAnimation { property: "scale"; from: 1.0; to: 0; duration: 200 }
NumberAnimation { property: "opacity"; from: 1.0; to: 0; duration: 200 }
}
removeDisplaced: Transition {
NumberAnimation { properties: "x,y"; duration: 500; easing.type: Easing.OutBack }
}
}
Finally, note that some behaviours can be obtained by using Shaders and combining animation on the elements and transitions on the delegate/elements of the delegate. A nice example is Tweet Search, in which a shading effect (see [ShaderEffect][5]) on the bar item is combined with a simple Transition on ListView add.
EDIT
Provide a customized scrolling like in the examples requires to take in account the position of the Items inside the ListView. A key to a working solution is to find a way to calculate the current position of the Item inside the visible part of the view and use that value to calculate the appropriate transformation. ListView derives from Flickable which has several useful properties for this purpose.
However, the y property of the Item is referred to the overall height of the content inside the ListView. To have its position w.r.t. the beginning of the visible area we can use the contentY property. A picture is worth a thousand words in this case:
The difference between y and contentY provides a value which can be used to calculate the required transformation factor (maybe in relation to the height of the ListView). Indeed, as the ListView is flicked, the two values and their difference change and so changes the transformation factor for a specific Item.
Such transformation covers only part of the problem. Once the flicking/movement ends the Items animation must be "finished" to make all the visible items usable. For this purpose we can exploit Binding and its when property to activate the finishing animation only when required, i.e. when flicking or dragging ends.
Given all this (boring) introduction, let's take in account the second animation (the simpler one). Here we can use scale to obtain the desired effect. The delegate code inside the ListView looks like the following:
ListView {
id: list
model: 100
spacing: 10
delegate: Rectangle {
id: itemDelegate
property int listY: y - list.contentY // stores the difference between the two values
width: parent.width
height: 50
border.color: "lightgray"
color: "red"
Binding {
target: itemDelegate
property: "scale"
value: 1 - listY / list.height / 2 // the "scale" property accepts values in the range [0, 1]
when: list.moving || list.flicking || list.dragging // ...when moved around
}
Binding {
target: itemDelegate
property: "scale"
value: 1 // flick finished --> scale to full size!
when: !(list.moving || list.dragging) // not moving or dragging any more
}
Behavior on scale {
NumberAnimation { duration: 100; to: 1}
enabled: !(list.flicking || list.dragging) // active only when flick or dragging ends!
}
}
}
The first Binding define the scaling factor on the basis of listY whereas the second one set the scaling to 1 but only when the ListView is not moving. The final Behavior is necessary to smooth the transition to the fully scaled Item.
The third effect can be obtained in a similar fashion with a Rotation:
ListView {
anchors.fill: parent
id: list
spacing: 10
model: 100
delegate: Rectangle {
id: itemDelegate
property int listY: y - list.contentY
property real angleZ: (90 * listY) / list.height // 0 - 90 degrees
transform: Rotation { origin.x: width / 2; origin.y: 30; axis { x: 1; y: 0; z: 0 } angle: angleZ}
//transform: Rotation { origin.x: 0; origin.y: 30; axis { x: 1; y: 1; z: 0 } angle: angleZ} <--- I like this one more!
width: parent.width
height: 50
border.color: "lightgray"
color: "red"
Binding {
target: itemDelegate
property: "angleZ"
value: 0
when: !(list.moving || list.dragging)
}
Behavior on angleZ {
NumberAnimation {duration: 200; to: 0}
enabled: !(list.flicking || list.dragging)
}
}
}
This time I've choosen to (arbitrarily) use only one Binding. The same could have been made for the first example, i.e. we could have written in the first delegate scale: 1 - listY / list.height / 2.
Following a similar approach you can also create the first animation and others. For the first animation I think that combining a Rotation with a Translate should suffice.
After many hours of work, research and #BaCaRoZzo's great help (Thanks #BaCaRoZzo), I finally found the right solution. Just use Component.onCompleted() event handler to run the animation associated with each delegate.
Here is an example, enjoy!
import QtQuick 2.3
ListView {
anchors.fill: parent
id: list
model: 100
cacheBuffer: 50
delegate: Rectangle {
id: itemDelegate
Component.onCompleted: showAnim.start();
transform: Rotation { id:rt; origin.x: width; origin.y: height; axis { x: 0.3; y: 1; z: 0 } angle: 0}// <--- I like this one more!
width: parent.width
height: 50
color: index % 2 === 0 ? "#EEE" : "#DDD"
SequentialAnimation {
id: showAnim
running: false
RotationAnimation { target: rt; from: 180; to: 0; duration: 800; easing.type: Easing.OutBack; property: "angle" }
}
}
}
A PathView displays data from models created from built-in QML types like ListModel and XmlListModel, or custom model classes defined in C++ that inherit from QAbstractListModel.
The view has a model, which defines the data to be displayed, and a delegate, which defines how the data should be displayed. The delegate is instantiated for each item on the path. The items may be flicked to move them along the path.

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
}
}
}
}

Resources