How to change delegate's property during transition - qt

I try to change property "opacity" in delegate (Rectangle as delegate), but get ReferenceError: rectDelegate is not defined.
How do I refer to the desired model index in the delegate to change the property?
Item {
id: root
QtObject {
id: internal;property int rectHeight: 16 * 3
}
GridView {
id: grid
model: 12; anchors.centerIn: parent; height: internal.rectHeight * 2; width: internal.rectHeight * 6
cellWidth: internal.rectHeight; cellHeight: internal.rectHeight
delegate: Rectangle {
id: rectDelegate
color: "green"
opacity: {
switch (index) {
case 0:
case 2:
case 5:
return 1
case 6:
...
}
}
width: internal.rectHeight
height: internal.rectHeight
}
}
states: [
State {
name: "first"
PropertyChanges {
target: rectDelegate
opacity: {
switch (rectDelegate.index) {
case 0:
...
}
}
}
},
State {
name: "second"
PropertyChanges {
target: rectDelegate
opacity: {
switch (rectDelegate.index) {
case 0:
...
}
}
}
}
]
transitions: [
Transition {
from: "first"
to: "second"
PropertyAnimation {...}
},
Transition {
from: "second"
to: "first"
PropertyAnimation {...}
}
]
Timer {
...
onTriggered: {rectDelegate.state = "second"}
}
}

Avoid using JS-code (and switchs) in QML as much as possible. The more declarative code you have - the better.
Try to move values for opacities of your delegates to array in internal.
Here is fixed & 'compilable' code (with my additions in places where ... was)
import QtQuick 2.12
import QtQuick.Controls 2.5
ApplicationWindow {
id: window
visible: true
width: 640
height: 480
title: qsTr("Stack")
Item {
id: root
state: "first"
QtObject {
id: internal;
property int rectHeight: 16 * 3
property var opacities: [0.7, 1, 1, 0.5, 1, 1, 0.7, 1, 1, 0.5, 1, 1]
}
GridView {
id: grid
model: 12; anchors.centerIn: parent; height: internal.rectHeight * 2; width: internal.rectHeight * 6
cellWidth: internal.rectHeight; cellHeight: internal.rectHeight
delegate: Rectangle {
id: rectDelegate
color: "green"
opacity: internal.opacities[index]
Behavior on opacity { NumberAnimation { duration: 1000 } }
width: internal.rectHeight
height: internal.rectHeight
}
}
states: [
State {
name: "first"
PropertyChanges {
target: internal
opacities: [0.7, 1, 1, 0.5, 1, 1, 0.7, 1, 1, 0.5, 1, 1]
}
},
State {
name: "second"
PropertyChanges {
target: internal
opacities: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
}
}
]
}
Timer {
interval: 2000
running: true
repeat: false
onTriggered: root.state = "second";
}
}

Related

How to trigger An Action Once After NumberAnimation Is Completed In QML

I am trying to call a reset function once after the animation is completed. But in the below given code , the animation starts after resetting the values
On Long press of the button I need to start an animation which basically acts as a progress bar, and once the animation is completed I need to call the reset() function.
I have tried the below given code , But here the animation starts once after the resetting of values are done.
Button {
id: button1
onPressAndHold: {
rectangle.visible = true
timer.restart()
}
}
Item {
id: rectangle
Behavior on width {
NumberAnimation {
duration: 1000
easing.type: Easing.InOutCubic
}
}
Image {
id: img
source: "somesource"
fillMode: Image.PreserveAspectCrop
}
}
Timer {
id: timer
repeat: true
interval: 50
onTriggered: {
rectangle.width = img.sourceSize.width * img.progress
if (rectangle.width <= img.sourceSize.width) {
timer.stop()
reset(values)
}
}
}
can you please let me know on how modify it such that the animation completes first and then the reset is done. Thank you in advance!
Ok, that really works for standalone Animation only that sounds a bit strange for me since the common usecase is using animations inside Behavior or State.
So you can use NumberAnimation.onRunningChanged or ScriptAction as #iam_peter said or use Transition.onRunningChanged as well:
Window {
height: 200
width: 600
visible: true
title: qsTr("Animation test")
RowLayout {
width: parent.width
height: 100
anchors.centerIn: parent
Button {
text: "start"
onClicked: {
testRect.state = "state2"
}
}
Rectangle {
id: testContainer
Layout.fillWidth: true
Layout.fillHeight: true
Rectangle {
id: testRect
height: 100
width: 0
color: "orange"
states: [
State {
name: "state1"
PropertyChanges {
target: testRect
width: 0
}
},
State {
name: "state2"
PropertyChanges {
target: testRect
width: testContainer.width
}
}
]
transitions: Transition {
NumberAnimation {
property: "width"
duration: 2000
easing.type: Easing.InQuad
}
onRunningChanged: {
if(running == false)
{
finishRect.color = "red";
}
}
}
}
}
Rectangle {
id: finishRect
Layout.preferredWidth: 100
color: "yellow"
width: 100
height: width
radius: width / 2
}
}
}
According to this post you have two options.
You can us the onRunningChanged handler and check if the animation is still running. If not call anything you want at that point.
Rectangle {
id: rect
width: 100; height: 100
color: "red"
Behavior on width {
NumberAnimation {
duration: 1000
onRunningChanged: {
if (!running)
console.log("Animation finished")
}
}
}
MouseArea {
anchors.fill: parent
onClicked: rect.width = 50
}
}
The other option would be to create a SequentialAnimation and add a SrcriptAction that runs after the NumberAnimation is completed.
Rectangle {
id: rect
width: 100; height: 100
color: "red"
Behavior on width {
SequentialAnimation {
NumberAnimation { duration: 1000 }
ScriptAction {
script: console.log("Animation finished")
}
}
}
MouseArea {
anchors.fill: parent
onClicked: rect.width = 50
}
}

Why is animation triggered on load

When the window opens you can see the Rectangle sliding out. I set the y property to the parent height so it should be initially outside of the window why is this being animated?
My guess it's because of the parent:height. Maybe because parent.height is not available at loading time and it's initially set to 0?
I have following example to reproduce:
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Rectangle {
id: test;
y: parent.height;
states: [
State {
name: "slideOut"
PropertyChanges{
target: test;
y: parent.height;
}
},
State {
name: "slideIn"
PropertyChanges{
target: test;
y: 0;
}
}
]
Behavior on y {
NumberAnimation {
duration: 500;
}
}
color: "red";
width: parent.width;
height: parent.height;
}
MouseArea {
anchors.fill: parent;
onClicked: {
if(test.state == "slideIn") {
test.state = "slideOut";
} else {
test.state = "slideIn";
}
}
}
}
Your guess sounds spot on to me.
You should use transitions with states instead:
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Rectangle {
id: test
y: parent.height
width: parent.width
height: parent.height
color: "red"
states: [
State {
name: "slideOut"
PropertyChanges {
target: test
y: parent.height
}
},
State {
name: "slideIn"
PropertyChanges {
target: test
y: 0
}
}
]
transitions: [
Transition {
NumberAnimation {
property: "y"
duration: 500
}
}
]
}
MouseArea {
anchors.fill: parent
onClicked: {
if (test.state == "slideIn") {
test.state = "slideOut"
} else {
test.state = "slideIn"
}
}
}
}
Another solution could be to use the enabled property of Behavior to only run the animation when the window is ready. I'm not sure which property you'd base it on though. Some ideas:
enabled: window.height > 0
enabled: window.active

QML List View Drag and Drop

I would like to create two qml list views that can perform two functions:
Drag and drop items within one list to change order of items
Drag and drop items cross lists. Items will remove from one list and add to another
Based on the Drag and Drop example in Qt Documentation, I decided to create two list views that accessing the same list model. Each item in the list model has a key as a bool value. Using DelegateModel with a filter, I can display items with key equals to true in one list and with false in the second list. When dragging an item from first list to second list, the key value will be flipped.
Now, I am facing two problems:
onDropped is not executed
DelegateModel is not refreshed after key value changed
Can anybody help me solve these problems or maybe have a better solution?
import QtQuick 2.5
import QtQuick.Window 2.0
import QtQml.Models 2.2
import QtQuick.Controls 2.2
Window {
visible: true
width: 300
height: 400
title: qsTr("List View Test")
property bool dropAreaOneEntered: false
property bool dropAreaTwoEntered: false
property int listNumer: 0
Rectangle {
id: root
width: parent.width
height: parent.height
ListView {
id: displayListOne
width: 100; height: parent.height
model: displayListOneModel
property int dragItemIndex: -1
moveDisplaced: Transition {
NumberAnimation{
properties: "x,y"
duration: 200
}
}
}
ListView {
id: displayListTwo
width: 100; height: parent.height
anchors.right: parent.right
model: displayListTwoModel
property int dragItemIndex: -1
moveDisplaced: Transition {
NumberAnimation{
properties: "x,y"
duration: 200
}
}
}
// ..........................................................
ListModel {
id: itemsListModel
// #disable-check M16
ListElement { colore: "blue"; key: true }
// #disable-check M16
ListElement { colore: "orange"; key: false }
// #disable-check M16
ListElement { colore: "blue"; key: true }
// #disable-check M16
ListElement { colore: "orange"; key: false }
// #disable-check M16
ListElement { colore: "blue"; key: true }
// #disable-check M16
ListElement { colore: "orange"; key: false }
// #disable-check M16
ListElement { colore: "blue"; key: true }
// #disable-check M16
ListElement { colore: "orange"; key: false }
}
// ..........................................................
DelegateModel {
id: displayListOneModel
model: itemsListModel
delegate: MouseArea {
id: mouseOneArea
width: 100; height: 100
property bool held: false
drag.target: held ? itemOneBlock : undefined
drag.axis: Drag.XAndYAxis
property var draggedItem: null
onPressAndHold: {
held = true
listNumer = 1
displayListOne.dragItemIndex = index
}
onReleased: {
listNumer = 0
held = false
}
Rectangle {
id: itemOneBlock
anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter }
width: 100
height: 100
color: colore
opacity: mouseOneArea.held ? 0.8 : 1.0
Drag.active: mouseOneArea.held
Drag.source: mouseOneArea
Drag.hotSpot.x: width / 2
Drag.hotSpot.y: height / 2
states: State{
when: mouseOneArea.held
ParentChange { target: itemOneBlock; parent: root }
AnchorChanges {
target: itemOneBlock
anchors { horizontalCenter: undefined; verticalCenter: undefined }
}
}
}
DropArea {
id: listOneDropArea
anchors.fill: parent
property bool flag: false
onExited: dropAreaOneEntered = false
onEntered: {
dropAreaOneEntered = true
if (dropAreaOneEntered & ! dropAreaTwoEntered){
if (listNumer == 1){
displayListOneModel.items.move(drag.source.DelegateModel.itemsIndex,
mouseOneArea.DelegateModel.itemsIndex)
}
}
}
onDropped: {
console.log("onDropped has been executed!")
}
}
}
groups: [
DelegateModelGroup {
includeByDefault: true
name: "listOne"
}
]
filterOnGroup: "listOne"
Component.onCompleted: {
var rowCount = itemsListModel.count;
for( var i = 0; i < items.count; i++ ) {
var entry = items.get(i).model;
if((entry.key != true)) {
items.removeGroups(i,1,"listOne")
}
}
}
}
DelegateModel {
id: displayListTwoModel
model: itemsListModel
delegate: MouseArea {
id: mouseTwoArea
width: 100; height: 100
property bool held: false
drag.target: held ? itemTwoBlock : undefined
drag.axis: Drag.XAndYAxis
onPressAndHold: {
held = true
listNumer = 2
displayListTwo.dragItemIndex = index
console.log("current index is "+index)
}
onReleased: {
listNumer = 0
held = false
}
Rectangle {
id: itemTwoBlock
anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter }
width: 100
height: 100
color: colore
opacity: mouseTwoArea.held ? 0.8 : 1.0
Drag.active: mouseTwoArea.held
Drag.source: mouseTwoArea
Drag.hotSpot.x: width / 2
Drag.hotSpot.y: height / 2
states: State{
when: mouseTwoArea.held
ParentChange { target: itemTwoBlock; parent: root }
AnchorChanges {
target: itemTwoBlock
anchors { horizontalCenter: undefined; verticalCenter: undefined }
}
}
}
DropArea {
id: listTwoDropArea
anchors.fill: parent
onExited: dropAreaTwoEntered = false
onEntered: {
dropAreaTwoEntered = true
if (dropAreaTwoEntered & ! dropAreaOneEntered){
if (listNumer == 2){
displayListTwoModel.items.move(drag.source.DelegateModel.itemsIndex,
mouseTwoArea.DelegateModel.itemsIndex)
}
}
}
onDropped: {
console.log("Flip the key value here!")
}
}
}
groups: [
DelegateModelGroup {
includeByDefault: true
name: "listTwo"
}
]
filterOnGroup: "listTwo"
Component.onCompleted: {
var rowCount = itemsListModel.count;
for( var i = 0; i < items.count; i++ ) {
var entry = items.get(i).model;
if((entry.key != false)) {
items.removeGroups(i,1,"listTwo")
}
}
}
}
}
}

Creating QML States dynamically

I want to make an icon component that changes it picture and color depending on it state:
StateIcon.qml:
import QtQuick 2.0
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0
Item {
Layout.preferredWidth: appLayout.icon.prefWidth
Layout.preferredHeight: appLayout.icon.prefHeight
property variant stateImage: stateImageInstance
Image {
id: stateImageInstance
width: appLayout.icon.prefWidth
height: appLayout.icon.prefWidth
sourceSize.width: width
sourceSize.height: height
}
property variant imageOverlay: imageOverlayInstance
ColorOverlay {
id: imageOverlayInstance
anchors.fill: stateImage
source: stateImage
}
transitions: Transition {
SequentialAnimation {
NumberAnimation {
target: stateImage; property: "scale"
to: 0; duration: 100
}
PropertyAction {
target: stateImage; property: "source"
}
PropertyAction {
target: imageOverlay; property: "color"
}
NumberAnimation {
target: stateImage; property: "scale"
to: 1; duration: 100
}
}
}
}
The problem is that I have to define states in the component instance:
main.qml:
StateIcon {
id: stateIcon
states: [
State {
name: "state1";
PropertyChanges {
target: stateIcon.stateImage
source: "qrc:/resources/icons/icon1.svg"
}
PropertyChanges {
target: stateIcon.imageOverlay; color: "gray"
}
},
State {
name: "state2";
PropertyChanges {
target: stateIcon.stateImage
source: "qrc:/resources/icons/icon2.svg"
}
PropertyChanges {
target: stateIcon.imageOverlay; color: "green"
}
}
...
]
state: "state1"
}
And now I want to know is it possible to define only state names, color and source in some array:
main.qml:
StateIcon {
id: stateIcon
rawStates: [
{
name: "state1",
iconSource: "qrc:/resources/icons/state1.svg",
color: "green"
},
{
name: "state2",
iconSource: "qrc:/resources/icons/state2.svg",
color: "green"
},
...
]
state: "state1"
}
And in the StateIcon.qml define states property dynamically using rawStates property?
Maybe something like that:
StateIcon.qml:
import QtQuick 2.0
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0
Item {
property variant rawStates
Layout.preferredWidth: appLayout.icon.prefWidth
Layout.preferredHeight: appLayout.icon.prefHeight
Image {
id: stateImage
width: appLayout.icon.prefWidth
height: appLayout.icon.prefWidth
sourceSize.width: width
sourceSize.height: height
}
ColorOverlay {
id: imageOverlay
anchors.fill: stateImage
source: stateImage
}
states: [
for(var i=0; i<rawStates.length; ++i) {
?
}
]
transitions: Transition {
SequentialAnimation {
NumberAnimation {
target: stateImage; property: "scale"
to: 0; duration: 100
}
PropertyAction {
target: stateImage; property: "source"
}
PropertyAction {
target: imageOverlay; property: "color"
}
NumberAnimation {
target: stateImage; property: "scale"
to: 1; duration: 100
}
}
}
}
Instead of using States I would use a plain javascript associative arrays.
You can't use transitions but you could use Behavior instead. Not anything can be done with behavior but it's enough most of the time.
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQml 2.2
ApplicationWindow {
id: mainWindow
visible: true
minimumWidth: 500
minimumHeight: 500
Row {
Rectangle {
id: rect
width: 100
height: 100
property var stateDescriptors: {
'state0': {color: 'green'},
'state1': {color: 'red'},
'state2': {color: 'blue'},
'state3': {color: 'purple'},
'state4': {color: 'orange'}
}
property string iconState: "state0"
Text {
anchors.fill: parent
text: parent.iconState
}
color: stateDescriptors[iconState].color
Behavior on iconState {
SequentialAnimation {
NumberAnimation {
target: rect; property: "scale"
to: 0; duration: 100
}
PropertyAction { } //actually change the iconState here, since the color is binded to it, it will also change between the 2 scale animations
NumberAnimation {
target: rect; property: "scale"
to: 1; duration: 100
}
}
}
}
Button {
text: 'change state'
property int count: 0
onClicked: {
count = (count + 1) % Object.keys(rect.stateDescriptors).length
rect.iconState = 'state' + count
}
}
}
}
Maybe this helps you:
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQml 2.2
ApplicationWindow {
id: mainWindow
visible: true
minimumWidth: 500
minimumHeight: 500
Row {
Rectangle {
id: rect
width: 100
height: 100
Text {
anchors.fill: parent
text: parent.state
}
property var myStates: []
states: myStates
onStateChanged: console.log(Object.keys(rect.states))
}
Button {
text: 'add state'
onClicked: {
rect.myStates.push(statePrototype.createObject(rect,
{
name: 'state' + count,
color: Qt.rgba(Math.random(count),
Math.random(count),
Math.random(count),
Math.random(count))
}))
rect.myStatesChanged()
count++
}
}
Button {
text: 'change state'
onClicked: {
rect.state = 'state' + (count1 % count)
count1++
}
}
}
property int count: 0
property int count1: 0
Component {
id: statePrototype
State {
id: st
property color color
PropertyChanges {
target: rect
color: st.color
}
}
}
}
It seems to be not so easily possible to add States to states directly. With the extra mile going over a custom property var myStates it suddenly works. Don't forget to tell everyone, that myStatesChanged() after adding something!
EDIT Once more, with the list of JS Objects, and a Instantiator. The method is the same
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQml 2.2
ApplicationWindow {
id: mainWindow
visible: true
minimumWidth: 500
minimumHeight: 500
Row {
Rectangle {
id: rect
width: 100
height: 100
Text {
anchors.fill: parent
text: parent.state
}
property var myStates: []
states: myStates
onStateChanged: console.log(Object.keys(rect.states))
}
Button {
text: 'change state'
property int count: 0
onClicked: {
rect.state = 'state' + count % rect.myStates.length
count ++
}
}
Button {
text: 'add states'
onClicked: {
stateDescriptors.push( { name: 'state' + stateDescriptors.length, color: Qt.rgba(Math.random(1),
Math.random(2),
Math.random(3),
Math.random(4)) })
stateDescriptorsChanged()
}
}
}
Instantiator {
model: stateDescriptors
delegate: State {
name: modelData.name
PropertyChanges {
target: rect
color: modelData.color
}
Component.onCompleted: {
console.log('created', modelData.name)
rect.myStates.push(this)
rect.myStatesChanged()
}
Component.onDestruction: {
console.log('destroy', modelData.name)
rect.myStates.pop()
}
}
}
property var stateDescriptors: [
{
name: 'state0',
color: 'green'
},
{
name: 'state1',
color: 'red'
},
{
name: 'state2',
color: 'blue'
},
{
name: 'state3',
color: 'purple'
},
{
name: 'state4',
color: 'orange'
}
]
}

Changing state after a transition's animations have finished

I'd like to change state after a transition's animations have completed. I have the following code that achieves this, although it seems kind of hackish:
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.2
Rectangle {
id: root
width: 400
height: 400
Rectangle {
id: rect
color: "blue"
width: 50
height: 50
anchors.centerIn: parent
MouseArea {
anchors.fill: parent
onClicked: rect.state = "animating"
}
states: [
State {
name: "animating"
PropertyChanges {
target: rect
rotation: 360
}
},
State {
name: "shrinking"
PropertyChanges {
target: rect
scale: 0
}
}
]
transitions: [
Transition {
from: ""
to: "animating"
SequentialAnimation {
RotationAnimation {
duration: 500
}
ScriptAction {
script: rect.state = "shrinking"
}
}
},
Transition {
from: "animating"
to: "shrinking"
NumberAnimation {
property: "scale"
duration: 500
}
}
]
}
}
Is there a nicer way to do this without using ScriptAction? Note that I need the second state, and I don't want to just consolidate the scale animation into the SequentialAnimation of the animating transition.
The proper way is to change the state in the runningChanged handler of the transition, when running pass to false than the animation finished.
to do that you have two solutions:
Sol 1. use connections ( you will get a warning about a none notifiable property, ignore it)
Connections{
target:rect.transitions[0]
onRunningChanged:{
if( rect.transitions[0].running === false)
{
rect.state = "shrinking"
}
}
}
the code will be :
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.2
Rectangle {
id: root
width: 400
height: 400
Rectangle {
id: rect
color: "blue"
width: 50
height: 50
anchors.centerIn: parent
MouseArea {
anchors.fill: parent
onClicked: rect.state = "animating"
}
states: [
State {
name: "animating"
PropertyChanges {
target: rect
rotation: 360
}
},
State {
name: "shrinking"
PropertyChanges {
target: rect
scale: 0
}
}
]
Connections{
target:rect.transitions[0]
onRunningChanged:{
if( rect.transitions[0].running === false)
{
rect.state = "shrinking"
}
}
}
transitions: [
Transition {
from: ""
to: "animating"
RotationAnimation {
duration: 500
}
},
Transition {
from: "animating"
to: "shrinking"
NumberAnimation {
property: "scale"
duration: 500
}
}
]
}
}
solution 2:
change state in runningchanged handler in the transition directly:
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.2
Rectangle {
id: root
width: 400
height: 400
Rectangle {
id: rect
color: "blue"
width: 50
height: 50
anchors.centerIn: parent
MouseArea {
anchors.fill: parent
onClicked: rect.state = "animating"
}
states: [
State {
name: "animating"
PropertyChanges {
target: rect
rotation: 360
}
},
State {
name: "shrinking"
PropertyChanges {
target: rect
scale: 0
}
}
]
transitions: [
Transition {
from: ""
to: "animating"
RotationAnimation {
duration: 500
}
onRunningChanged:{
if( running === false)
{
rect.state = "shrinking"
}
}
},
Transition {
from: "animating"
to: "shrinking"
NumberAnimation {
property: "scale"
duration: 500
}
}
]
}
}
I prefer the first solution (Connections) cause it's more generic
A slightly different approach is to set the shrinking state in the animating state, and use a PropertyAction to force the state change to happen at the end of the transition:
State {
name: "animating"
PropertyChanges {
target: rect
rotation: 360
}
PropertyChanges {
target: rect
state: "shrinking"
}
and
Transition {
SequentialAnimation {
RotationAnimation {
duration: 500
}
PropertyAction {
target: rect
property: "state"
}
}
}
Note that I agree with jturcotte on his assessment of using these states here.

Resources