QML How reverse play animation - qml

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

Related

How to rotate only the right or left part of an image

I want to simulate the closure of one page of a book.
Any suggestions on how to do that in qml?
Thanks in advance
Perhaps Flipable, in combination with the fillMode property of Image:
import QtQuick 2.0
import QtQuick.Window 2.0
Window {
id: window
width: 640
height: 480
visible: true
Image {
id: backgroundImage
source: "http://www.thebookdesigner.com/wp-content/uploads/2013/04/pages-vs-spreads.png"
anchors.centerIn: parent
Flipable {
id: flipable
anchors.fill: parent
anchors.leftMargin: parent.width / 2
property bool flipped: false
front: Image {
id: foldImage
source: backgroundImage.source
fillMode: Image.Pad
width: foldImage.implicitWidth / 2
horizontalAlignment: Image.AlignRight
}
back: Image {
source: backgroundImage.source
width: foldImage.implicitWidth / 2
fillMode: Image.Pad
horizontalAlignment: Image.AlignLeft
}
transform: Rotation {
id: rotation
origin.x: 0
origin.y: flipable.height / 2
axis.x: 0; axis.y: 1; axis.z: 0 // set axis.y to 1 to rotate around y-axis
angle: 0 // the default angle
}
states: State {
name: "back"
PropertyChanges {
target: rotation;
angle: -180
}
when: flipable.flipped
}
transitions: Transition {
NumberAnimation {
target: rotation
property: "angle"
duration: 1000
easing.type: Easing.InCubic
}
}
MouseArea {
anchors.fill: parent
onClicked: flipable.flipped = !flipable.flipped
}
}
}
}
Flipable does what its name suggests, and the fillMode property in combination with a width that is too small for the entire image allows you to "reposition" the contents of the image within the bounds of the item.
So, the front item contains the right side of the image, and the back item contains the left side. With an actual book that has many pages, you'd have to use the relevant pages instead of the same one.

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

QML Dialog is broken?

I have this code:
import QtQuick 2.3
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.2
Dialog {
standardButtons: StandardButton.Ok | StandardButton.Cancel
width: layout.implicitWidth
height: layout.implicitHeight
RowLayout {
id: layout
anchors.fill: parent
Item {
width: 10
height: 1
}
GridLayout {
columns: 2
rowSpacing: 10
Layout.fillHeight: true
Layout.fillWidth: true
Text {
text: "Hello world? "
}
Text {
text: "Hello world!"
}
Text {
text: "Goodbye world? "
}
Text {
text: "Goodbye world!"
}
}
Item {
width: 10
height: 1
}
}
}
When you run it it looks like this, and the dialog can be resized to any size. Also the RowLayout actually doesn't fill its parent as you can see.
How can I make it so that the dialog can't be resized below the minimum size of the layout, and so that the layout fills the dialog?
Unfortunately this is a bug in Qt. Currently the documentation is misleading and Dialog does not size itself correctly to the contents. Consider this working example, which I based on the DefaultFontDialog:
AbstractDialog {
title: "Hello"
id: root
// standardButtons: StandardButton.Ok | StandardButton.Cancel
modality: Qt.NonModal
Rectangle {
id: content
implicitWidth: mainLayout.implicitWidth + outerSpacing * 2
implicitHeight: mainLayout.implicitHeight + outerSpacing * 2
property real spacing: 6
property real outerSpacing: 12
color: "white"
GridLayout {
id: mainLayout
anchors { fill: parent; margins: content.outerSpacing }
rowSpacing: content.spacing
columnSpacing: content.spacing
columns: 5
Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" }
Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" }
Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" }
Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" }
}
}
}
This works exactly as expected, though of course you don't get the buttons.
If you just change it to a Dialog and uncomment the standardButtons, then it stops working - you can resize the dialog to clip its contents (width-wise at least), and the contents do not expand to the dialog size.
The reason for the minimum width not working becomes clear when we look at the source code for Dialog (in qtquickcontrols/src/dialogs/DefaultDialogWrapper.qml):
AbstractDialog {
id: root
default property alias data: defaultContentItem.data
onVisibilityChanged: if (visible && contentItem) contentItem.forceActiveFocus()
Rectangle {
id: content
property real spacing: 6
property real outerSpacing: 12
property real buttonsRowImplicitWidth: minimumWidth
property bool buttonsInSingleRow: defaultContentItem.width >= buttonsRowImplicitWidth
property real minimumHeight: implicitHeight
property real minimumWidth: Screen.pixelDensity * 50
implicitHeight: defaultContentItem.implicitHeight + spacing + outerSpacing * 2 + buttonsRight.implicitHeight
implicitWidth: Math.min(root.__maximumDimension, Math.max(
defaultContentItem.implicitWidth, buttonsRowImplicitWidth, Screen.pixelDensity * 50) + outerSpacing * 2);
minimumWidth is hardcoded to Screen.pixelDensity * 50!! There was never any hope that it would match the dialog contents. minimumHeight does work better (though not perfect, I believe because the spacing isn't considered).
I'm not sure why the defaultContentItem does not expand correctly, but anyway. It looks like the only solution at the moment is to use AbstractDialog and implement the buttons and accepted()/rejected()/etc. signals yourself. Bit of a pain.
Edit / Solution
I did some further investigation.
The reason the defaultContentItem doesn't expand is because it's bottom anchor isn't tied to the top of the button row:
Item {
id: defaultContentItem
anchors {
left: parent.left
right: parent.right
top: parent.top
margins: content.outerSpacing
}
implicitHeight: childrenRect.height
}
Minimum sizes just don't work that well with anchor-based layouts. They do with GridLayout-based layouts.
Unfortunately childrenRect has no implicitWidth/Height so we have to actually have the child items go into a ColumnLayout rather than be the ColumnLayout.
...
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.1
import QtQuick.Window 2.2
// A Dialog that resizes properly. The defualt dialog doesn't work very well for this purpose.
AbstractDialog {
id: root
default property alias data: defaultContentItem.data
onVisibilityChanged: if (visible && contentItem) contentItem.forceActiveFocus()
Rectangle {
id: content
property real spacing: 6
property real outerSpacing: 12
property real buttonsRowImplicitWidth: minimumWidth
property bool buttonsInSingleRow: defaultContentItem.width >= buttonsRowImplicitWidth
property real minimumHeight: implicitHeight
property real minimumWidth: implicitWidth // Don't hard-code this.
implicitWidth: Math.min(root.__maximumDimension, Math.max(Screen.pixelDensity * 10, mainLayout.implicitWidth + outerSpacing * 2))
implicitHeight: Math.min(root.__maximumDimension, Math.max(Screen.pixelDensity * 10, mainLayout.implicitHeight + outerSpacing * 2))
color: palette.window
Keys.onPressed: {
event.accepted = true
switch (event.key) {
case Qt.Key_Escape:
case Qt.Key_Back:
reject()
break
case Qt.Key_Enter:
case Qt.Key_Return:
accept()
break
default:
event.accepted = false
}
}
SystemPalette { id: palette }
// We use layouts rather than anchors because there are no minimum widths/heights
// with the anchor system.
ColumnLayout {
id: mainLayout
anchors { fill: parent; margins: content.outerSpacing }
spacing: content.spacing
// We have to embed another item so that children don't go after the buttons.
ColumnLayout {
id: defaultContentItem
Layout.fillWidth: true
Layout.fillHeight: true
}
Flow {
Layout.fillWidth: true
id: buttonsLeft
spacing: content.spacing
Repeater {
id: buttonsLeftRepeater
Button {
text: (buttonsLeftRepeater.model && buttonsLeftRepeater.model[index] ? buttonsLeftRepeater.model[index].text : index)
onClicked: root.click(buttonsLeftRepeater.model[index].standardButton)
}
}
Button {
id: moreButton
text: qsTr("Show Details...")
visible: false
}
}
Flow {
Layout.fillWidth: true
id: buttonsRight
spacing: content.spacing
layoutDirection: Qt.RightToLeft
Repeater {
id: buttonsRightRepeater
// TODO maybe: insert gaps if the button requires it (destructive buttons only)
Button {
text: (buttonsRightRepeater.model && buttonsRightRepeater.model[index] ? buttonsRightRepeater.model[index].text : index)
onClicked: root.click(buttonsRightRepeater.model[index].standardButton)
}
}
}
}
}
function setupButtons() {
buttonsLeftRepeater.model = root.__standardButtonsLeftModel()
buttonsRightRepeater.model = root.__standardButtonsRightModel()
if (!buttonsRightRepeater.model || buttonsRightRepeater.model.length < 2)
return;
var calcWidth = 0;
function calculateForButton(i, b) {
var buttonWidth = b.implicitWidth;
if (buttonWidth > 0) {
if (i > 0)
buttonWidth += content.spacing
calcWidth += buttonWidth
}
}
for (var i = 0; i < buttonsRight.visibleChildren.length; ++i)
calculateForButton(i, buttonsRight.visibleChildren[i])
content.minimumWidth = calcWidth + content.outerSpacing * 2
for (i = 0; i < buttonsLeft.visibleChildren.length; ++i)
calculateForButton(i, buttonsLeft.visibleChildren[i])
content.buttonsRowImplicitWidth = calcWidth + content.spacing
}
onStandardButtonsChanged: setupButtons()
Component.onCompleted: setupButtons()
}
You have to use it a bit differently to a normal Dialog. Just imagine it is a ColumnLayout (this is a slightly different example to the original question):
ColumnLayoutDialog {
id: dialog1
standardButtons: StandardButton.Ok | StandardButton.Cancel
Text {
text: "Hello world? "
}
Text {
text: "Hello world!"
}
// Spacer.
Item {
Layout.fillHeight: true;
}
Text {
text: "Goodbye world? "
}
Text {
text: "Goodbye world!"
}
}
By the way you could change the ColumnLayout to a GridLayout and expose the columns property if you want. That might make more sense.
A small issue
It turns out a QWindow's minimum width and height only ensure that the dialog isn't actively resized to be less than its content. It doesn't ensure that the dialog is never smaller than its content, because the content can grow after the dialog is created (e.g. extra items added). To workaround this I added this function to my ColumnLayoutDialog:
// The minimumWidth/Height values of content are accessed by the C++ class, but they
// only ensure that the window isn't resized to be smaller than its content. They
// don't ensure that if the content grows the window grows with it.
function ensureMinimumSize()
{
if (root.width < content.minimumWidth)
root.width = content.minimumWidth;
if (root.height < content.minimumHeight)
root.height = content.minimumHeight;
}
It has to be called manually when you change the dialog contents. Or to do it automatically you can add this to the content rectangle:
onMinimumHeightChanged: {
if (root.height < content.minimumHeight)
root.height = content.minimumHeight;
}
onMinimumWidthChanged: {
if (root.width < content.minimumWidth)
root.width = content.minimumWidth;
}
This is a bug in QT up to version 5.6.0. Most likely the bug number 49058. The code from the question works as expected in QT 5.6.1 and 5.7.0.
A partial workaround for the old versions is to remove the lines
width: layout.implicitWidth
height: layout.implicitHeight
and replace
anchors.fill: parent
with
anchors.right: parent.right
anchors.left: parent.left
The dialog then respects the minimum height and the contents expand horizontally.
Here is also a complete workaround, but it relies on undocumented implementation details of Dialog, so it should be used with caution. It works in 5.5.1, 5.6.0, 5.6.1 and 5.7.0. Note also that the second Item is changed to a red Rectangle to make the behavior more apparent.
import QtQuick 2.3
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.2
Dialog {
visible: true
standardButtons: StandardButton.Ok | StandardButton.Cancel
RowLayout {
id: layout
// In the horizontal direction, expansion and shrinking can be achieved with anchors.
anchors.left: parent.left
anchors.right: parent.right
// Used only for guessing the height of the Dialog's standard buttons.
Button {
id: hiddenButton
visible: false
}
// Repeats until the relevant parts of the dialog (parent of the parent of the RowLayout)
// are complete, then overwrites the minimum width and implicit height and stops repeating.
Timer {
id: timer
interval: 50; running: true; repeat: true;
onTriggered: {
if(layout.parent.parent) {
var lp = layout.parent
var lpp = layout.parent.parent
lpp.minimumWidth = layout.implicitWidth + 2 * lpp.outerSpacing
layout.buttonHeight = 2 * lpp.outerSpacing + hiddenButton.implicitHeight + lpp.spacing
lp.implicitHeight = layout.implicitHeight + 2 * lpp.outerSpacing
running = false
}
}
}
// The guessed space needed for the Dialog's buttons.
property int buttonHeight: 80
// Expand and shrink vertically when the dialog is resized.
height: parent.parent ? Math.max(parent.parent.height-buttonHeight, implicitHeight) : implicitHeight
Item {
width: 10
height: 1
}
GridLayout {
columns: 2
rowSpacing: 10
Layout.fillHeight: true
Layout.fillWidth: true
Text {
text: "Hello world? "
}
Text {
text: "Hello world!"
}
Text {
text: "Goodbye world? "
}
Text {
text: "Goodbye world!"
}
}
Rectangle {
Layout.fillHeight: true
color: 'red'
width: 10
}
}
}

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.

Bounds of Flickable.contentY in QML

What is the correct way of asking the bounds of Flickable.contentY? I need that for a scrollbar.
Experimentally I have discovered that
offsetY <= contentY <= offsetY + contentHeight - height
where offsetY can be calculated as
var offsetY = contentY-Math.round(visibleArea.yPosition*contentHeight)
offsetY is zero at the start of the application and seems to be constant unless Flickable is resized.
This formula works in general, but there probably should be a dedicated function for it.
I did a scrollbar easily without offset :
// ScrollBar.qml
import QtQuick 2.0
Rectangle {
id: scrollbar;
color: "#3C3C3C";
visible: (flicker.visibleArea.heightRatio < 1.0);
property Flickable flicker : null;
width: 20;
anchors {
top: flicker.top;
right: flicker.right;
bottom: flicker.bottom;
}
Rectangle {
id: handle;
height: (scrollbar.height * flicker.visibleArea.heightRatio);
color: "#5E5E5E";
border {
width: 1;
color: "white";
}
anchors {
left: parent.left;
right: parent.right;
}
Binding { // Calculate handle's x/y position based on the content position of the Flickable
target: handle;
property: "y";
value: (flicker.visibleArea.yPosition * scrollbar.height);
when: (!dragger.drag.active);
}
Binding { // Calculate Flickable content position based on the handle x/y position
target: flicker;
property: "contentY";
value: (handle.y / scrollbar.height * flicker.contentHeight);
when: (dragger.drag.active);
}
MouseArea {
id: dragger;
anchors.fill: parent;
drag {
target: handle;
minimumX: handle.x;
maximumX: handle.x;
minimumY: 0;
maximumY: (scrollbar.height - handle.height);
axis: Drag.YAxis;
}
}
}
}
Just use it like this :
Flickable {
id: myFlick;
}
ScrollBar {
flicker: myFlick;
}
It can be moved with mouse, and moves automatically when Flickable is scrolled.