API Documentation
Documentation for developing SailfishOS applicationsCreating applications with Sailfish Silica Sailfish Application Features
Introduction to Sailfish Silica
The Sailfish Silica module provides the essential components for building the user interface of a Sailfish application.
It provides a set of UI components (based on the QtQuick component set) as well as utilities for styling your Sailfish applications and managing application behaviours. Using the Silica module to build your application UI not only makes it easier to implement through the Silica component set, but also ensures your app has the same look and feel as other Sailfish applications.
Creating Sailfish applications
Sailfish applications are simply QML-based applications: they are written through a combination of QML and C++, where the UI is built using QML components from the Silica and QtQuick modules (and any other third-party modules) and then launched from a C++ application by embedding the main QML file in a QQuickView.
The easiest way to create a Sailfish application is with the Sailfish SDK. The Sailfish SDK documentation shows how to create a Sailfish UI project to build a simple application. This project automatically creates a QML file with a simple UI definition and the C++ application to launch it.
The QML file is what defines the UI to be displayed by your application. Let's look at how you can expand this UI using various features of the Sailfish Silica module.
A simple Sailfish application UI
Here is a simple Sailfish application UI that shows a line of text in the center of the screen:
import QtQuick 2.2 import Sailfish.Silica 1.0 ApplicationWindow { initialPage: Component { Page { Label { text: "Hello world!" anchors.centerIn: parent } } } }
There are three core aspects in creating this UI:
- The
QtQuick
andSailfish.Silica
modules are imported in order to use their QML types - A Silica ApplicationWindow is declared as the top-level component
- The displayed content is set by configuring the window to initially display a Silica Page with a centered Silica Label
ApplicationWindow is the top-level type of all Sailfish applications. This is the window that contains the application UI, and every window has a page stack that displays the current screen, or page, of the application. The ApplicationWindow initialPage property sets the first page displayed by the application, so in the above example, the app will initially show a single Page with a Label in the center. The initialPage can be set as a QML file URL, a Page instance, or a Component
with a top-level Page (as in the above example).
Now that we have a simple application, we can expand the UI using some of the QML types from the Silica module.
Styling a simple Sailfish UI
Building on the previous example, here is an app that displays a slider, as well as a button that when clicked will add the slider's current value to a list:
import QtQuick 2.2 import Sailfish.Silica 1.0 ApplicationWindow { initialPage: Component { Page { Column { width: parent.width // an interactive slider with a 0-100 range that steps by 1 when slided. Slider { id: slider label: "A simple slider" width: parent.width minimumValue: 0; maximumValue: 100; stepSize: 1 valueText: value } Button { text: "Save" anchors.horizontalCenter: parent.horizontalCenter onClicked: { listModel.append({"sliderValue": "Value: " + slider.value}) } } Repeater { model: ListModel { id: listModel } Label { text: model.sliderValue } } } } } }
As in the earlier example, the application and its initial page of content are created through the ApplicationWindow and Page QML types. Additionally, the app uses:
- The Silica Slider type to show a horizontal slider
- The Silica Button type to show a clickable button
- The Silica Label type to show some text
- The Column, ListModel and Repeater types from the standard QtQuick module for arranging the items within the layout and populating the list of slider values. Repeater creates an instance of its top-level child (in this case, a Label) for every entry in its model.
Although the UI functions as intended, it is missing some things that would give it the look and feel of a Sailfish application and also make it more presentable and usable. For example:
- It would be ideal if the slider values could be removed from the list
- The list entries are placed right next to the left edge of the page, instead of leaving some margin between the page edge and the list content
- The page does not have any heading text
- The page cannot be flicked downwards if the list of numbers exceeds the height of the page
These can be fixed by using some additional Silica types as well as the Theme object to style the UI appropriately.
Adding ListItem
for list entries
The Silica ListItem type provides a standard UI component for list entries, with an optional ContextMenu. A context menu is a set of menu options that appear for an individual list item, allowing the user to trigger actions associated with it.
Here, the use of ListItem allows each list entry to be turned into an interactive item with a set of associated actions. This is done by making the repeater's top-level child a ListItem instead, and moving the Label into this child. Then, the ListItem menu property is set to a ContextMenu, with a MenuItem that triggers the removal of the item from the model:
Repeater { model: ListModel { id: listModel } ListItem { width: parent.width Label { text: model.sliderValue anchors.verticalCenter: parent.verticalCenter } menu: ContextMenu { MenuItem { text: "Remove" onClicked: { listModel.remove(model.index) } } } } }
Now when the user presses and holds down on a list entry, the menu appears, allowing the removal of the item from the list.
Perhaps it would be better if the list entry was not removed immediately, in case the user should confirm its removal or be allowed to cancel the action. In Sailfish OS, this is applied through the remorse action concept, which allows the user to cancel a destructive action before its imminent execution. The RemorseItem and RemorsePopup types are used to present this option to the user: instead of performing the action immediately, the user is temporarily given the option of cancelling the action.
However, with a ListItem it's not necessary to use RemorseItem or RemorsePopup directly: as a convenience, the ListItem remorseAction() function internally shows a remorse action then call a specified function when the countdown finishes. So, modify the menu item's onClicked handler to call remorseAction() instead and pass in the function that calls ListModel remove() if the countdown reaches zero:
Repeater { model: ListModel { id: listModel } ListItem { id: listEntry // add an id so this can be referenced from onClicked() below width: parent.width Label { text: model.sliderValue anchors.verticalCenter: parent.verticalCenter } menu: ContextMenu { MenuItem { text: "Remove" onClicked: { listEntry.remorseAction("Deleting", function() { listModel.remove(model.index) }) } } } } }
The label text still doesn't look quite right, though: the convention in Sailfish apps is that interactive items (that is, items that respond when pressed, like a Button or the list items in this example) color their text with a highlight-type color when pressed, or the default color otherwise. Also, there should be spacing between each list item and the left edge of the page, as well as spacing above and below the "Save" button.
Both of these issues can be fixed in a manner standard to Sailfish applications by styling with the Theme object.
Styling with the Theme object
The Theme object provides the common style characteristics of Sailfish UI components. You can use it to apply the standard Sailfish colors, fonts, sizes, margins and other look and feel characteristics to your UI.
For instance, Theme.horizontalPageMargin provides a standard size for the space between the edges of the page and the items within it, so this can be used to add a left-edge margin to the list entries in the example. Also, the Theme.primaryColor and Theme.highlightColor provide the default and highlight colors of the current Sailfish ambience, so bind the label's color property in each list entry to the appropriate color using the list item's highlighted property (which is true
when the item is highlighted through a press action):
ListItem { id: listEntry width: parent.width Label { color: listEntry.highlighted ? Theme.highlightColor : Theme.primaryColor // color the text appropriately text: model.sliderValue x: Theme.horizontalPageMargin anchors.verticalCenter: parent.verticalCenter } menu: ContextMenu { MenuItem { text: "Remove" onClicked: { listEntry.remorseAction("Deleting", function() { listModel.remove(model.index) }) } } } }
To add space between the slider and the button, as well as between the button and the list, set the column's spacing
to Theme.paddingLarge, a standard value for space between items on a page. Since this space should not be added between the items in the list of slider values, place the Repeater and its children within a new Column that uses the default zero-value spacing:
Page { // main column Column { width: parent.width spacing: Theme.paddingLarge // add spacing between items in main column // Slider { ... } // Button { ... } // sub-column that contains the Repeater and its list items Column { width: parent.width // Repeater { ... } } } }
Further improvements
Generally, every page in a Silica application should display a header at the top, and it should be possible to scroll through the list of items if there are a lot of entries in the list.
The header can be added with a Silica PageHeader above the main column, and the list can be made scrollable by placing the Column within a SilicaFlickable. Additionally, you can add a VerticalScrollDecorator child to the flickable. This displays a vertical bar along the right edge of the flickable when it is flicked, to hint at the scroll position and total area of the flickable.
Page { PageHeader { id: header title: "Sailfish example" } SilicaFlickable { anchors { top: header.bottom bottom: parent.bottom left: parent.left right: parent.right } contentHeight: mainColumn.height // main column Column { id: mainColumn // ... rest of main column contents } VerticalScrollDecorator {} } }
It would also be useful to allow the list to be cleared completely. This could be done with pulley menus: these are menus that are normally hidden but are slowly revealed when the parent flickable is dragged. In this case a PullDownMenu — a pulley menu that is shown at the top of a page — can be added to the SilicaFlickable:
SilicaFlickable { PullDownMenu { MenuItem { text: "Clear" onClicked: listModel.clear() } } // ... rest of flickable code }
(It would be nice if the list was not cleared immediately but instead allowed the user to cancel the action within a few seconds, as was done using the remorseAction() function in the earlier example. In this case, it can be achieved with a RemorsePopup and calling execute() - try it out!)
Replacing the column list with a SilicaListView
Currently, the list data is displayed using a combination of SilicaFlickable, Column and Repeater. One thing to be aware of when using a Column is that all of its children are always created, even those that are currently off-screen. In this case, this is fine if the list is fairly small, but if there are hundreds or thousands of items, or if the delegates are complex with lots of details to be drawn, then the application performance is likely to suffer.
Instead, it would be preferable to use a SilicaListView, which is based on QtQuick's ListView type and only creates and renders the delegates that are visible (or are off-screen within the designated cacheBuffer). To do this, replace the SilicaFlickable with a SilicaListView, and move the model
and delegate
of the Repeater into the list view:
Page { SilicaListView { anchors.fill: parent model: ListModel { id: listModel } delegate: ListItem { // ... rest of list item contents } // sub-column that contained the Repeater and its list items is no longer necessary // and can be removed VerticalScrollDecorator {} PullDownMenu { MenuItem { text: "Clear" onClicked: listModel.clear() } } } }
Additionally, the PageHeader and the main Column with the Slider and Button should be moved into the header of the list view, so that when it is scrolled, those components are scrolled together with the delegates of the list view. To do this, the items can be grouped together into a new layout using another Column:
SilicaListView { header: Column { width: parent.width height: header.height + mainColumn.height + Theme.paddingLarge // add space between button and first list item PageHeader { id: header title: "Sailfish example" } // main column Column { id: mainColumn // ... rest of main column contents } } // ... rest of SilicaListView code }
The finished result
In summary, to create a basic Sailfish UI with the Silica module:
- Create a top-level ApplicationWindow and give it an initialPage
- Use Silica components like Label, Button and Slider and ListItem to get started with displaying data and receiving user input
- Use Theme to style your UI and apply standard colors, sizes and margins
- Enhance your UI with features like pulley menus (using PullDownMenu and PushUpMenu) and remorse-type actions (using ListItem's built-in features, or separately with RemorseItem and RemorsePopup)
Here is the completed example code:
import QtQuick 2.2 import Sailfish.Silica 1.0 ApplicationWindow { initialPage: Component { Page { SilicaListView { anchors.fill: parent header: Column { width: parent.width height: header.height + mainColumn.height + Theme.paddingLarge PageHeader { id: header title: "Sailfish example" } Column { id: mainColumn width: parent.width spacing: Theme.paddingLarge Slider { id: slider label: "A simple slider" width: parent.width minimumValue: 0; maximumValue: 100; stepSize: 1 valueText: value } Button { text: "Save" anchors.horizontalCenter: parent.horizontalCenter onClicked: { listModel.append({"sliderValue": "Value: " + slider.value}) } } } } model: ListModel { id: listModel } delegate: ListItem { id: listEntry width: parent.width Label { color: listEntry.highlighted ? Theme.highlightColor : Theme.primaryColor text: model.sliderValue x: Theme.horizontalPageMargin anchors.verticalCenter: parent.verticalCenter } menu: ContextMenu { MenuItem { text: "Remove" onClicked: { listEntry.remorseAction("Deleting", function() { listModel.remove(model.index) }) } } } } PullDownMenu { MenuItem { text: "Clear" onClicked: listModel.clear() } } VerticalScrollDecorator {} } } } }
Other useful Silica types
Aside from the Silica types previously mentioned, here are some other commonly used types:
- TextField and TextArea - text input controls
- TextSwitch, ProgressBar and ComboBox - some common UI controls
- SilicaGridView - grid view built on QtQuick's GridView type
- SectionHeader and Separator - visually separates components within a layout
- Dialog - a specialized page with "Accept" and "Cancel" header buttons for user confirmation and cancellation actions
See the Sailfish Silica Reference for the full list and reference documentation.
Creating applications with Sailfish Silica Sailfish Application Features