How to animate adding and removing element to Layout in QML? - qt

I want to animate adding and removing of TextField in ColumnLayout. Basically I want to animate like this:- https://doc.qt.io/qt-5/videos/viewtransitions-basic.mp4

As a ColumnLayout uses the implicitHeight to position the items, you can add an animation to that to create the sliding effect. I used a wrapping Item to preserve the implicitHeight of the TextField itself. It may not look perfect, but I don't think you cannot get that much further
Item {
visible: your_property_here
implicitHeight: visible ? text.implicitHeight : 0
Behavior on implicitHeight { NumberAnimation { duration: 500 } }
TextField {
id: text
anchors.fill: parent
scale: parent.visible ? 1 : 0.2
Behavior on scale { NumberAnimation { duration: 500 } }
}
}

I didn't found effective answer. Until then I'm using column as workaround
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
Page {
id: page_root
component ETextField : TextField {
width: root.width/2
}
state: "LOGIN"
Column {
anchors.centerIn: parent
ETextField {
id:host
placeholderText: "Host e.g:- exam.server.com:8080"
}
ETextField {
id:uane
placeholderText: "Username"
}
ETextField {
id: passwd
placeholderText: "Password"
}
ETextField {
id: confirm_passwd
placeholderText: "Confirm Password"
}
RowLayout {
Button {
id: submit
text: "Login"
}
Button {
id: change_state
text: "Register Instead"
onClicked: page_root.state = page_root.state === "LOGIN" ? "REGISTER" : "LOGIN"
}
}
}
states: [
State {
name: "LOGIN"
PropertyChanges {
target: confirm_passwd
height: passwd.height
}
PropertyChanges {
target: change_state
text : "Register Instead"
}
},
State {
name: "REGISTER"
PropertyChanges {
target: confirm_passwd
height: 0
}
PropertyChanges {
target: change_state
text : "Login Instead"
}
}
]
transitions: [
Transition {
from: "LOGIN"
to: "REGISTER"
NumberAnimation {
target: confirm_passwd
property : "height"
duration: 100
}
},
Transition {
from: "REGISTER"
to: "LOGIN"
NumberAnimation {
target: confirm_passwd
property : "height"
duration: 100
}
}
]
}

Related

Focusable Qml Components (Focus My Control On Tab)

i have created a custom button in qml, which i want to focus on pressing 'tab', i mean "jump to it on pressing tab if its on top of queue" by default qml has this functionality on button itself and some of other controls too but what about new components which does not exist,i saw a "FocusScope" control of qml but there is no documentation of using it and i am not sure how can i implement it, here is my control :
import QtQuick 2.4
Item {
id: button
width: innerText.width + 10
height: 30
property alias text: innerText.text;
property alias font: innerText.font;
property color color: "#00171f"
property color hoverColor: "#00395f"
property color pressColor: "#3E65FF"
property int fontSize: 12
property int borderWidth: 0
property int borderRadius: 2
property bool highlighted : true
onEnabledChanged: state = ""
signal clicked
property var background
Rectangle {
id: rectangleButton
anchors.fill: button
radius: borderRadius
color: button.enabled ? button.color : "grey"
border.width: borderWidth
border.color: "black"
Text {
id: innerText
font.pointSize: fontSize
font.family: "B Nazanin"
color: "white"
anchors.centerIn: rectangleButton
}
}
//change the color of the button in differen button states
states: [
State {
name: "Hovering"
PropertyChanges {
target: rectangleButton
color: hoverColor
}
},
State {
name: "Pressed"
PropertyChanges {
target: rectangleButton
color: pressColor
}
}
]
//define transmission for the states
transitions: [
Transition {
from: ""; to: "Hovering"
ColorAnimation { duration: 200 }
},
Transition {
from: "*"; to: "Pressed"
ColorAnimation { duration: 10 }
},
Transition {
from: "*"
to: ""
ColorAnimation { duration: 200 }
}
]
//Mouse area to react on click events
MouseArea {
hoverEnabled: true
anchors.fill: button
onEntered: { button.state='Hovering'}
onExited: { button.state=''}
onClicked: { button.clicked();}
onPressed: { button.state="Pressed" }
onReleased: {
if (containsMouse)
button.state="Hovering";
else
button.state="";
}
}
}
It looks like you are simply looking for the activeFocusOnTab property:
Item {
id: button
activeFocusOnTab: true
// ...
}

auto KeyNavigation.tab for custom components "rectangle or item" in QML

in Qml there is auto key navigation for already known components like checkbox,textfield,button and etc , i have my custom component which is a item or rectangle and i want same functionality for it,without writing
KeyNavigation.tab: componentid
here is one of my custom controls :
Rectangle {
signal clicked
property alias font : icoText.font.family
property alias icon : icoText.text
property alias size : icoText.font.pixelSize
property alias toolTip : tooltipText.text
property string colorEnter :"#0481ff"
property string colorExit :"#00171f"
id: root
implicitWidth: 50
implicitHeight: 50
//width: childrenRect.width
radius: 0
//height: childrenRect.height
color: colorExit
state: "default"
Text {
id: icoText
text: ""
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
font.pixelSize: 25
font.family: "fontawesome"
visible: text!= ""
color: "white"
}
ToolTip {
id:tooltipText
text: ""
delay: 500
timeout: 2000
visible: mouseArea.containsMouse && text!=""
font.family: "B Nazanin"
contentItem: Text {
text: tooltipText.text
font: tooltipText.font
color: "white"
}
background: Rectangle {
color: "#cc000000"
border.color: "black"
}
}
InnerShadow {
id:shadow
anchors.fill: icoText
radius: 1.0
samples: 17
horizontalOffset: 1
color: colorExit
source: icoText
visible: false
}
MouseArea{
id: mouseArea
anchors.fill: parent
hoverEnabled: true
onEntered: root.color = colorEnter
onExited: root.color = root.state == "transparent"? "transparent" : root.colorExit
onPressed: {
shadow.visible = true
}
onReleased: {
shadow.visible = false
}
onClicked: {
root.clicked()
}
}
states: [
State {
name: "transparent"
PropertyChanges {
target: root
color:"transparent"
}
PropertyChanges {
target: icoText
color:colorExit
}
},
State{
name: "default"
PropertyChanges {
target: root
color:colorExit
}
PropertyChanges {
target: icoText
color:"white"
}
}
]
}
which will be inside a page like this :
Item{
myControl{
}
myControl{
}
}
this component by default does not loop through pressing tab what should i do?
i already tried this without success, i think this should be inside FocusScope but cause of poor documentation i did not get a simple example for this
set activeFocusOnTab on parent and focus:true in child you want to get focus
Rectangle{
activeFocusOnTab: true
Control{
focus: true
}
}
Focusable Qml Components (Focus My Control On Tab)
For my experience key navigation only works with native components like checkbox, textfield, button, etc.
To work arround this problem I used a fake native component hided with the same size of my custom component like the next example:
Rectangle {
id: myCustomComponent1
width: 100
height: 100
color: red
Button {
id: buttonFake1
text: "My Accessible text Component 1"
width: parent.width
height: parent.height
opacity: 0 // hide the fake component
Accessible.role: Accessible.defaultButton
Accessible.name: text
KeyNavigation.tab: buttonFake2
onClicked: {
console.log(index)
}
onFocusChanged: {
if(focus === true){
// Here do what you want with your custom component
// For example, change color, size, ...
Do_what_you_Want()
// And then set back the focus at the fake native component
// to key navigation keeps working from the same component
buttonFake1.focus = true
}
}
}
}
Rectangle {
id: myCustomComponent2
width: 100
height: 100
color: green
Button {
id: buttonFake2
text: "My Accessible text Component 2"
width: parent.width
height: parent.height
opacity: 0 // hide the fake component
Accessible.role: Accessible.defaultButton
Accessible.name: text
KeyNavigation.tab: buttonFake1
onClicked: {
console.log(index)
}
onFocusChanged: {
if(focus === true){
// Here do what you want with your custom component
// For example, change color, size, ...
Do_what_you_Want()
// And then set back the focus at the fake native component
// to key navigation keeps working from the same component
buttonFake2.focus = true
}
}
}
}

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

Custom Button inheritance (like in OOP rules)

I have made custom Button in QML and I want it to be the base item for all buttons in my project. What I want is to derive (like in OOP) buttons from it to change functionality. At first, every derived button should have its own onClicked response.
Is this possible in QML and if it is, how?
It's possible.
Here is an example of my BaseButton type. When BaseButton is clicked it emits a custom signal, in my code it's sgnClicked.
BaseButton.qml
import QtQuick 2.0
Item {
id:idButton
signal sgnClicked()
signal sgnClickMaintained()
signal sgnEntered()
signal sgnExited()
signal sgnReleased()
signal sgnPressed()
signal sgnCanceled()
property alias label: idText
property alias text: idText.text
property string iconSourceUp:""
property string iconSourceDown:""
property string iconSourceDisabled:""
property alias backgroundWidth: background.width
property alias backgroundHeight: background.height
property alias backgroundRect: backgroundRect
property alias hover: touchArea.enabled
width: 100
height: 20
Text{
id:idText
//text: "Button"
color:"white"
anchors.centerIn: parent
font.pointSize: 12
}
//if maintained at each 300 ms resend signal
Timer{
id:timer
running: false
repeat: true
interval: 300
onTriggered: sgnClickMaintained()
}
Image{
id:background
z: -1
width: parent.width
height: parent.height
source:iconSourceUp
visible: source.toString() !== ""
}
Gradient {
id:idGradient
GradientStop { position: 0 ; color: touchArea.pressed ? "#ccc" : "#eee" }
GradientStop { position: 1 ; color: touchArea.pressed ? "#aaa" : "#ccc" }
}
Rectangle {
id: backgroundRect
width: parent.width
height: parent.height
border.color: "#888"
color: enabled ?"":"lightgrey"
radius: 4
z: -1
visible: !background.visible
gradient: enabled ?idGradient:null
}
MouseArea{
id: touchArea
anchors.fill: parent
hoverEnabled: true
onCanceled: idButton.sgnCanceled()
onClicked:
{
idButton.sgnClicked()
}
onEntered: idButton.sgnEntered()
onExited: idButton.sgnExited()
onPressAndHold: {
timer.restart()
idButton.sgnClickMaintained()
}
onReleased:{
idButton.state = "up"
timer.stop()
idButton.sgnReleased()
}
onPressed:
{
idButton.state = "down"
idButton.sgnPressed()
}
}
onEnabledChanged: {
if(enabled === false)
{
idButton.state = "disabled"
timer.stop()
}
else{
idButton.state = "up"
}
}
states: [
State {
name: "down"
PropertyChanges {
target: background
source: iconSourceDown
}
},
State {
name: "disabled"
PropertyChanges {
target: background
source: iconSourceDisabled
}
},
State {
name: "up"
PropertyChanges {
target: background
source: iconSourceUp
}
}
]
Component.onCompleted:{
if(enabled)
{
state = "up"
}
else
{
state = "disabled"
}
}
}
To make a new Button inheriting from BaseButton you should instantiante the BaseButton as root item and in the slot onSgnClicked write what you want your button should do when clicked.
CustomButton.qml
import QtQuick 2.0
BaseButton {
width: 100
height: 50
onSgnClicked: {
//do something
}
}

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