Criar folhas sobrepostas

Aprender a lidar com as folhas sobrepostas.

Tornar a nossa aplicação útil

Temos uma janela, temos cartões e temos acções. Contudo, ainda precisamos de descobrir uma forma de introduzir um nome, descrição e data à nossa escolha.

Uma forma de o fazermos é criar uma nova página onde colocamos os elementos de entrada necessários. Contudo, uma página inteira dedicada apenas a solicitar um nome, descrição e data parece um pouco excessivo.

Em vez disso, iremos usar uma folha sobreposta.

Camada sobreposta que aparece no meio da aplicação, como se fosse uma janela de diálogo

Folha de adição de contagem

O novo componente que vamos adicionar é um Kirigami.OverlaySheet . As folhas sobrepostas ficam sobre o conteúdo da janela e poderão ser usadas para uma grande variedade de fins, como fornecer informações extra que sejam relevantes para o conteúdo actual. São uma espécie de janelas instantâneas especiais, com a diferença que não podem ser movidas.

Kirigami.OverlaySheet {
    id: addSheet
    header: Kirigami.Heading {
        text: i18nc("@title:window", "Add kountdown")
    }
    Kirigami.FormLayout {
        Controls.TextField {
            id: nameField
            Kirigami.FormData.label: i18nc("@label:textbox", "Name:")
            placeholderText: i18n("Event name (required)")
            onAccepted: descriptionField.forceActiveFocus()
        }
        Controls.TextField {
            id: descriptionField
            Kirigami.FormData.label: i18nc("@label:textbox", "Description:")
            placeholderText: i18n("Optional")
            onAccepted: dateField.forceActiveFocus()
        }
        Controls.TextField {
            id: dateField
            Kirigami.FormData.label: i18nc("@label:textbox", "Date:")
            placeholderText: i18n("YYYY-MM-DD")
            inputMask: "0000-00-00"
        }
        Controls.Button {
            id: doneButton
            Layout.fillWidth: true
            text: i18nc("@action:button", "Done")
            enabled: nameField.text.length > 0
            onClicked: {
                kountdownModel.append({
                    name: nameField.text,
                    description: descriptionField.text,
                    // O método parse() processa um texto e devolve o número de milisegundos desde 1
                    // de Janeiro de 1970, às 00:00:00 UTC.
                    date: Date.parse(dateField.text)
                });
                nameField.text = ""
                descriptionField.text = ""
                dateField.text = ""
                addSheet.close();
            }
        }
    }
}

Podemos atribuir às folhas sobrepostas um cabeçalho. O mesmo é atribuído através da propriedade header . Passámos o nosso com um Kirigami.Heading que contém um título relevante: 'Add Kountdown' (Adicionar uma Contagem).

Depois chegamos ao Kirigami.FormLayout . Este permite-nos criar facilmente formulários altamente adaptativos, os quais mostra as legendas dos campos e os campos propriamente ditos, tanto em ecrãs panorâmicos como em dispositivos móveis mais estreitos. Estas disposições de formulários estão desenhadas para lidarem com uma grande variedade de tipos de campos, ainda que para já nos foquemos com campos simples do tipo Controls.Textfield , que nos dão campos de texto simples onde poderemos escrever as coisas.

Criámos os elementos Textfield que funcionam como:

  1. Introdução do nome da nossa contagem
  2. Introdução da descrição da nossa contagem
  3. Introdução da data contra a qual estamos a fazer a contagem, que deverá ser indicada no formato AAAA-MM-DD.

Dentro de cada um destes elementos Controls.Textfield , estamos a configurar uma propriedade Kirigami.FormData.label que lhe permite definir legendas para eles. O formulário irá apresentar os textos correctos para cada um destes campos de texto. Iremos também definir o texto de substituição dentro dos campos com a propriedade TextField.placeholderText -- este texto irá desaparecer assim que o utilizador começar a escrever algo no campo. Finalmente, iremos também configurar a propriedade onAccepted para invocar o método forceActiveFocus() do campo seguinte; isto irá mudar o campo activo assim que o utilizador carregar na tecla ENTER, melhorando a usabilidade do nosso formulário.

Também definimos uma propriedade chamada inputMask no campo de texto da nossa data. Se configurarmos a mesma como "0000-00-00", impedimos que o utilizador escreva algo que possa quebrar a funcionalidade da aplicação (como por exemplo texto), restringindo-os a indicar apenas números e traços, que poderemos então usar para tentar processar um objecto de data.

No fim do nosso formulário estamos a incluir um Button que adiciona a nossa nova contagem decrescente ao modelo da lista. Configurámos a propriedade enabled com uma instrução condicional que verifica se o campo do nome está vazio ou não: se estiver, o botão fica desactivado, e vice-versa. Quando o botão for carregado, ele invoca o método append do nosso modelo de lista kountdownModel, adicionando um objecto de JavaScript que inclui as propriedades que fornecemos. Também nos certificamos que limpamos os campos de texto, configurando as suas propriedades text respectivas com um texto vazio. Finalmente, podemos invocar um método na nossa folha sobreposta, o close() , que a irá fechar.

Usar a nossa folha

actions.main: Kirigami.Action {
    id: addAction
    icon.name: "list-add"
    text: i18nc("@action:button", "Add kountdown")
    onTriggered: addSheet.open()
}

As folhas sobrepostas têm dois métodos, o open() e o close() , que controlam a abertura e o fecho deste componente. Neste caso, configurámos a folha para ser aberta quando activarmos a nossa acção. Assim que tivermos gravado os nossos ficheiros e compilado o nosso programa, seremos capazes de adicionar as nossas próprias contagens decrescentes personalizadas!

A nossa aplicação até agora

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import QtQuick 2.15
import QtQuick.Controls 2.15 as Controls
import QtQuick.Layouts 1.15
import org.kde.kirigami 2.20 as Kirigami

Kirigami.ApplicationWindow {
    id: root

    title: i18nc("@title:window", "Day Kountdown")

    globalDrawer: Kirigami.GlobalDrawer {
        isMenu: true
        actions: [
            Kirigami.Action {
                text: i18n("Quit")
                icon.name: "gtk-quit"
                shortcut: StandardKey.Quit
                onTriggered: Qt.quit()
            }
        ]
    }

    ListModel {
        id: kountdownModel
    }

    Component {
        id: kountdownDelegate
        Kirigami.AbstractCard {
            contentItem: Item {
                implicitWidth: delegateLayout.implicitWidth
                implicitHeight: delegateLayout.implicitHeight
                GridLayout {
                    id: delegateLayout
                    anchors {
                        left: parent.left
                        top: parent.top
                        right: parent.right
                    }
                    rowSpacing: Kirigami.Units.largeSpacing
                    columnSpacing: Kirigami.Units.largeSpacing
                    columns: root.wideScreen ? 4 : 2

                    Kirigami.Heading {
                        Layout.fillHeight: true
                        level: 1
                        text: i18n("%1 days", Math.round((date-Date.now())/86400000))
                    }

                    ColumnLayout {
                        Kirigami.Heading {
                            Layout.fillWidth: true
                            level: 2
                            text: name
                        }
                        Kirigami.Separator {
                            Layout.fillWidth: true
                            visible: description.length > 0
                        }
                        Controls.Label {
                            Layout.fillWidth: true
                            wrapMode: Text.WordWrap
                            text: description
                            visible: description.length > 0
                        }
                    }
                    Controls.Button {
                        Layout.alignment: Qt.AlignRight
                        Layout.columnSpan: 2
                        text: i18n("Edit")
                    }
                }
            }
        }
    }

    // Overlay sheets appear over a part of the window
    Kirigami.OverlaySheet {
        id: addSheet
        header: Kirigami.Heading {
            text: i18nc("@title:window", "Add kountdown")
        }
        // Form layouts help align and structure a layout with several inputs
        Kirigami.FormLayout {
            // Textfields let you input text in a thin textbox
            Controls.TextField {
                id: nameField
                // Provides label attached to the textfield
                Kirigami.FormData.label: i18nc("@label:textbox", "Name:")
                // Placeholder text is visible before you enter anything
                placeholderText: i18n("Event name (required)")
                // What to do after input is accepted (i.e. pressed enter)
                // In this case, it moves the focus to the next field
                onAccepted: descriptionField.forceActiveFocus()
            }
            Controls.TextField {
                id: descriptionField
                Kirigami.FormData.label: i18nc("@label:textbox", "Description:")
                placeholderText: i18n("Optional")
                onAccepted: dateField.forceActiveFocus()
            }
            Controls.TextField {
                id: dateField
                Kirigami.FormData.label: i18nc("@label:textbox", "Date:")
                placeholderText: i18n("YYYY-MM-DD")
                inputMask: "0000-00-00"
            }
            Controls.Button {
                id: doneButton
                Layout.fillWidth: true
                text: i18nc("@action:button", "Done")
                // Button is only enabled if the user has entered something into the nameField
                enabled: nameField.text.length > 0
                onClicked: {
                    // Add a listelement to the kountdownModel ListModel
                    kountdownModel.append({
                        name: nameField.text,
                        description: descriptionField.text,
                        date: Date.parse(dateField.text)
                    });
                    nameField.text = ""
                    descriptionField.text = ""
                    dateField.text = ""
                    addSheet.close();
                }
            }
        }
    }

    pageStack.initialPage: Kirigami.ScrollablePage {
        title: i18nc("@title", "Kountdown")

        // Kirigami.Action encapsulates a UI action. Inherits from Controls.Action
        actions.main: Kirigami.Action {
            id: addAction
            // Name of icon associated with the action
            icon.name: "list-add"
            // Action text, i18n function returns translated string
            text: i18nc("@action:button", "Add kountdown")
            // What to do when triggering the action
            onTriggered: addSheet.open()
        }

        Kirigami.CardsListView {
            id: layout
            model: kountdownModel
            delegate: kountdownDelegate
        }
    }
}

Imagem da aplicação com quatro cartões de exemplo