Reusable QML components
Introduction
This article would explain some reusable QML components - how they are implemented and their usage. If you wish to write some reusable QML components then you might be interested to read my approach to implement them. However, if you just want to use the components in your QML apps / games - you can just read the usage part of the components. These components were originally written by me as a part of KDiamond QML version game.
List dialog
A list dialog provides a dialog box with a list of items to choose from. Basically, a dialog that prompts for one element out of a list of elements.
Fig: ListDialog for selecting Difficulty Level
Basic Structure of List dialog
Here is the basic skeleton for list dialog. We have the listdialog Rectangle as the main container / parent and dialog Rectangle as a child. listdialog Rectangle would provide a semi-transparent background for the dialog. The dialog Rectangle contains the ListView - which would be used for displaying the list of elements in the dialog.
Rectangle{
id: listdialog
Rectangle{
id: dialog
ListView{
id: dialoglist
}
Text {
id: dialogtitle
}
Rectangle{
id: dialogline
}
}
}
listdialog also implement a MouseArea for dismissing the dialog when clicked. Essentially, the MouseArea would just contain a line of code to turn the opacity of the dialog to 0, so that it is not visible and thus dismissed. Dialog animations /transitions can be added in this mouse area as well.
dialogtitle is a text element which would act as the tile of the dialog. We would like this text to be dynamically assigned, hence we would need to expose the text property of dialogtitle through our component. This can be done by using property alias for dialogtitle.text
property alias dialogtitle: dialogtitle.text
ListView for List dialog
The ListView in our dialog would require a model and a delegate. Since we are designing the dialog as a reusable component - we would like to allow the model to be supplied from outside the component. So we expose the model of the ListView by setting a property alias for it.
property alias dialogModel: dialoglist.model
We then define the delegate for our ListView. This delegate would define how each list item would look on the ListView. After adding our delegate our ListView would look like below,
ListView{
id: dialoglist
width: parent.width
spacing: margin
height:model.count*(listitem_height+spacing)
interactive: false
anchors.top: dialogline.bottom
anchors.topMargin: margin
model: mymodel
onModelChanged: {
dialoglist.height = dialoglist.model.count*(listitem_height+spacing)
}
delegate: Rectangle{
id: listitem
width: parent.width - 50
height: listitem_height
radius: 10
anchors.horizontalCenter: parent.horizontalCenter
color: "teal"
Text {
id: listitemText
text: name
anchors.centerIn: parent
font.pixelSize: 14
color: "white"
}
}
}
}
We've used the onModelChanged event handler to dynamically assign the height of the ListView. This is useful when the model of the dialog is changed. The height of the dialog ListView is recalculated with the help of this event handler.
After this, the ascestetics have been taken care of. However, we still need to add a MouseArea to the delegate (list item) - we need to then emit a signal describing which item has been clicked. For this we would implement a clicked signal in the component,
signal clicked (string item_string, int index)
The clicked signal would emit both - the item index as well as them item string. The MouseArea would look something like this,
MouseArea{
id: delegateMouseArea
anchors.fill: parent
onClicked: {
listdialog.clicked(name, index)
}
}
Animating the dialog
To animate the list dialog when it appears and is dismissed, we use PropertyAnimation. PropertyAnimation provides a way to animate changes to a property's value. Here, we would like to animate the "opacity" property of the listdialog hence, we have used two PropertyAnimations.
PropertyAnimation { id: showDialog; target: listdialog; property: "opacity"; to: 1; duration: 500; easing.type: Easing.InQuad}
PropertyAnimation { id: hideDialog; target: listdialog; property: "opacity"; to: 0; duration: 500; easing.type: Easing.OutQuad}
The above two animations can be sumarized as below,
- showDialog which will change the opacity from 0 to 1 in an InQuad pattern
- hideDialog which will change the opacity from 1 to 0 in an OutQuad pattern
The duration for each animation is 500 milliseconds. Also, we need to expose these animations to show and hide the dialog with animations. We do this by adding alias for both the animations.running state.
property alias showDialog: showDialog.running
property alias hideDialog: hideDialog.running
Usage for List dialog
Using the List dialog is really simple. After adding the component to your project, you can add a list dialog in your main.qml by the following piece of code. You can define a simple list model to go with your list dialog, as I have done below.
// Model for new game dialog
ListModel{
id: newgameModel
ListElement{
name: "Timed game"
}
ListElement{
name: "Untimed game"
}
}
Listdialog{
id: newgameDialog
dialogtitle: "New Game"
dialogModel: newgameModel
opacity: 0
onClicked: {
// Put your logic here! Below is my logic from KDiamond QML version.
/* New game started */
MainWindow.newGameButton_clicked(index);
// Dismiss new game dialog
newgameDialog.hideDialog= true;
// Hide pop ups if any
hidePopup()
}
}
- To show the dialog use: newgameDialog.showDialog = true
- To hide the dialog use: newgameDialog.hideDialog = true
Fig: Screenshot of ListDailog in KDiamond
Popup
A popup is used to show small info or notifications to the user.
Implementation
Below is the basic structure for the popup. It is fairly simple and contains a Text component with the container rectangles. popUpMessage would be the message to be displayed to the user.
Rectangle{
id: popUpContainer
color: "transparent"
Rectangle{
id: popUp
Text{
id: popUpMessage
}
}
}
We expose the text in the popUpMessage component by using a property alias.
property alias popUpMessage: popUpMessage.text
We add animations by using PropertyAnimation - showPopUp and hidePopUp.
PropertyAnimation { id: showPopUp; target: popUp; property: "opacity"; to: 1; duration: 500; easing.type: Easing.Linear}
PropertyAnimation { id: hidePopUp; target: popUp; property: "opacity"; to: 0; duration: 500; easing.type: Easing.Linear}
Usage
To display the Popup component we need to create a PopUp element in main.qml and provide it with popUpMessage.
For e.g. below how we can display a popup when the game is paused.
Fig: Screenshot of pause game popup in KDiamond
// Pause game pop up
PopUp{
id: pausedPopUp
popUpMessage: "Game paused!"
}
- To show pop up use: pausedPopUp.showPopUp = true
- To hide pop up use: pausedPopUp.hidePopUp = true
Quit Dialog
Quit Dialog is a component which is a used to ask for confirmation before exiting an application or a game.
Implementation
The basic structure of quit dialog, given below, includes a rectangle dialog consisting of Text element for dialog title, another Text element for quit message. In addition to these, there are two buttons used - Yes button and No button. Both of these buttons contain Text area button_yes_text and button_no_text and MouseArea.
Rectangle{
id: quitDialog
Rectangle{
id: dialog
Text{
id: title
}
// Quit dialog text
Text{
id: dialogDesc
}
// Yes button
Button{
id:yes_button
Text {
id: button_yes_text
}
}
// No button
Button{
id:no_button
Text {
id: button_no_text
}
}
}
}
We make all the text on the quit dialog like - dialog text, yes button text and no button text - to be set from outside the component. We use property alias for this.
property alias quitMessage: dialogDesc.text
property alias yesButtonText: button_yes_text.text
property alias noButtonText: button_no_text.text
Since we wish to give the developer using this dialog ability to implement his own logic when one of the buttons is clicked, we implement two signals - yes_clicked and no_clicked which are emitted when the button is clicked.
signal yes_clicked
signal no_clicked
/* Yes button */
Button{
id:yes_button
width: 100
height: 50
color: "teal"
anchors.top: dialogDesc.bottom
anchors.topMargin: 20
anchors.left: parent.left
anchors.leftMargin: 30
Text {
id: button_yes_text
anchors.centerIn: parent
font.pixelSize: 14
font.bold: true
color: "white"
}
MouseArea{
anchors.fill: parent
onClicked: {
quitDialog.yes_clicked();
}
}
}
/* No button */
Button{
id:no_button
width: 100
height: 50
color: "teal"
anchors.left: yes_button.right
anchors.leftMargin: 20
anchors.top: dialogDesc.bottom
anchors.topMargin: 20
Text {
id: button_no_text
anchors.centerIn: parent
font.pixelSize: 14
font.bold: true
color: "white"
}
MouseArea{
anchors.fill: parent
onClicked: {
quitDialog.no_clicked();
}
}
}
We also implement the animations by using PropertyAnimation - showDialog and hideDialog - similar to List Dialog and popup.
Usage
You can use the quit dialog component in the following way,
Quitdialog{
id: quitDialog
width: parent.width
height: parent.height
quitMessage: "Are you sure you want to exit KDiamond?"
yesButtonText: "Yes"
noButtonText: "No"
onYes_clicked: Qt.quit();
onNo_clicked: {
// Put your logic here, below is my logic from Kdiamond QML version
// Dismiss the quit dialog
hideDialog= true;
// Resume the game
MainWindow.pauseButton_clicked(false);
}
}
Fig: Using quit dialog in KDiamond
Note: All the above components are used in the KDiamond QML version. You can access its latest code here.