API Documentation
Documentation for developing SailfishOS applicationsIntroduction to Sailfish Silica Scaling Sailfish Application UIs
Sailfish Application Features
When writing Sailfish applications, there are features and implementation patterns to be aware of to improve your app's presentation and user experience. The Sailfish Silica module provides ways to access these features and standardize your application UI so that it looks and feels like other Sailfish applications.
The application page stack
Every Sailfish application has a page stack that contains the screens of content to be shown in the application window. This allows the user to move between different screens, or pages, within the app; only one page on the stack is visible at any time. To show a new page, add it to the top. To close the current page and return to the one that was previously displayed, remove the page from the stack.
Pages are created with the Page type, and the application's page stack is accessible as a PageStack instance through the window's pageStack property. The first page in the stack, shown when the application starts, is set by the window's initialPage.
To add or push a page to the stack, call the stack's push() function with a QML file URL, or a Component with a top-level Page. To remove the page at the top of the stack (that is, the currently visible page), call the stack's pop() function. To get a reference to this top-most page, use currentPage, and to get the current number of pages in the stack, use depth.
Here's an example of the page stack in action. This allows pages to be continually added with push() or removed with pop():
import QtQuick 2.2 import Sailfish.Silica 1.0 ApplicationWindow { initialPage: pageComponent Component { id: pageComponent Page { PageHeader { title: "Page count: " + pageStack.depth } Column { width: parent.width anchors.centerIn: parent spacing: Theme.paddingLarge Button { text: "Push a page" anchors.horizontalCenter: parent.horizontalCenter onClicked: { pageStack.push(pageComponent) } } Button { text: "Pop this page" anchors.horizontalCenter: parent.horizontalCenter onClicked: { pageStack.pop() } } } } } }
(When the user swipes to the right or tap the top-left page indicator to move back to the previous page, this pops the page off the stack in the same way as pop().)
While you can pass a Component to the push() function to add the page to the stack, if the page is defined in a separate QML file, it's preferable to push the page using the URL instead:
Button { text: "Add a page" onClicked: pageStack.push(Qt.resolvedUrl("MyPage.qml")) }
This postpones the page compilation and construction until push() is called, which can create a noticable difference if the page is complex and takes some time to compile.
There are a number of other operations you can do with a PageStack. These include:
- Call push() with a set of initial properties for the page, or pass
PageStackAction.Immediate
to push the page immediately without the sliding animation to the new page - Call pop() and pass a page that is lower in the stack, so that all pages above it are popped
- Instead of push(), use replace() to replace the current page, rather than adding a new one
- Instead of push(), use pushAttached() to add a new page without displaying it, allowing the user to swipe forward to it instead
See the PageStack documentation for the full details.
Using dialogs for user input screens
Dialog are special types of pages that present content that needs to be confirmed or canceled by the user, or request user input while providing the ability to confirm or cancel the input operation. A Dialog can be swiped forwards as an action of confirmation by the user, or swiped backwards (as with an ordinary Page) as an act of cancelation. Additionally, dialogs use DialogHeader (rather than PageHeader) in order to display "Accept" and "Cancel" buttons at the top of the page.
Below is a page with a button that shows a dialog with a set of options when clicked:
import QtQuick 2.2 import Sailfish.Silica 1.0 ApplicationWindow { initialPage: firstPage Component { id: firstPage Page { PageHeader { id: header } Button { text: "Show dialog" anchors.centerIn: parent onClicked: { pageStack.push(dialogComponent) } } } } Component { id: dialogComponent Dialog { property string selectedOption: options.currentItem.text DialogHeader { id: header title: "Choose an option" } ComboBox { id: options label: "Options:" anchors.top: header.bottom width: parent.width menu: ContextMenu { MenuItem { text: "Option 1" } MenuItem { text: "Option 2" } MenuItem { text: "Option 3" } } } } } }
Dialog has accepted and rejected signals that are emitted when the dialog is accepted (closed by pressing "Accept" or swiping forwards) or rejected (closed by pressing "Cancel" or swiping backwards). To respond to the closing of the dialog and give some feedback about whether the selected option was confirmed by the user, modify the first page to connect to the accepted() signal to show the result:
Component { id: firstPage Page { PageHeader { id: header } Button { text: "Show dialog" anchors.centerIn: parent onClicked: { var dialog = pageStack.push(dialogComponent) dialog.accepted.connect(function() { header.title = "Last selection: " + dialog.selectedOption }) } } } }
Notice that PageStack::push() returns an instance of the page that was created when pushed onto the stack; in this case, it is an instance of the dialogComponent
. This allows the connection to the accepted() signal.
Since the page header's title is only updated when the dialog is accepted, if the user changes the selected option but presses "Cancel" rather than "Accept", the title will not be updated. This allows, for example, an app to only save a set of user-entered data to disk if a dialog was accepted, and not rejected.
Managing UI layout and orientation
Using Theme
for consistent and scalable layouts
The Theme object is configured to scale its sizes and margins according to the platform screen size and resolution, so it should always be used where possible in preference to hard-coded sizes and margins. Using Theme also ensures the application UI uses sizes and margins consistent with other Sailfish applications. For example:
- Theme.horizontalPageMargin should be used for the left and right margins between the edge of a Page and its contents
- ListItem objects with only an icon and a label next to each other typically use Theme.iconSizeMedium for the icon and Theme.fontSizeMedium (the default font size for a Label) for the text
- ListItem objects with two lines of text typically set the item's contentHeight to Theme.itemSizeMedium
- Theme.buttonWidthLarge should be used for a button with no other buttons beside it
See the Theme documentation for more details.
Dynamic layout updates for orientation changes
Sailfish applications can use a number of orientation-related properties to respond to changes in the device orientation to update the UI accordingly when the device is physically rotated. This allows the application UI to take full advantage of the available screen space in different orientations.
To do this:
- Set the page's allowedOrientations to the orientations it should support
- Use the page's isPortrait, isLandscape or orientation properties to update the UI when the orientation changes
For example, consider an app that shows an image and its basic metadata (filename, width and height). In portrait orientation, the UI show a horizontally-centered Image at the top, and a column of DetailItem objects below with the metadata:
import QtQuick 2.2 import Sailfish.Silica 1.0 ApplicationWindow { initialPage: Component { Page { id: page PageHeader { id: header title: "Image details" } Image { id: image anchors { top: header.bottom horizontalCenter: parent.horizontalCenter } sourceSize.width: page.width - (Theme.horizontalPageMargin * 2) fillMode: Image.PreserveAspectFit source: StandardPaths.pictures + "/img_0001.jpg" } Column { anchors { top: image.bottom topMargin: Theme.paddingLarge left: parent.left leftMargin: Theme.horizontalPageMargin right: parent.right rightMargin: Theme.horizontalPageMargin } DetailItem { label: "Name of file"; value: image.source } DetailItem { label: "Width"; value: image.width } DetailItem { label: "Height"; value: image.height } } } } }
To allow the page to respond to orientation changes, set the page's allowedOrientations to the orientations that should be supported by your UI. The value may be Orientation.Portrait
, Orientation.PortraitInverted
, Orientation.Landscape
, Orientation.LandscapeInverted
, or a mask of any combination of these. In addition there is Orientation.All
to support all of the possible orientations, which is what we shall use here:
Page { allowedOrientations: Orientation.All // rest of page code... }
Now when the device orientation changes, the application will update this page's orientation value to the current device orientation.
However, when the app is run with this new change, we can see that the current UI layout is not ideal in landscape orientations: if the image is large, the details below may go off-screen and will no longer be visible. Instead of adding a SilicaFlickable to allow the user to scroll down to view the image metadata, the layout could instead dynamically change when in landscape orientations to show the image on the left and the description on the right instead. To determine when the page is in portrait or landscape orientation, check the orientation value, or as a convenience, check isPortrait or isLandscape.
To make the required layout changes to the example:
- Clear the image's
horizontalCenter
anchor in landscape orientations so that the image automatically anchors itself to the left instead - Resize the image: in landscape orientation, it should fill half of the page width, and the right-edge margin is not necessary as it no longer sizes to the right edge of the screen
Here is the relevant example code with the changes applied:
Image { id: image anchors { top: header.bottom horizontalCenter: page.isPortrait ? parent.horizontalCenter : undefined } sourceSize.width: { var maxImageWidth = Screen.height/2 var leftMargin = Theme.horizontalPageMargin var rightMargin = page.isPortrait ? Theme.horizontalPageMargin : 0 return maxImageWidth - leftMargin - rightMargin } // rest of image code... }
Then, adjust the anchors of the column of metadata depending on the orientation:
Column { anchors { // in landscape, anchor the top to the bottom of the header instead of the image top: page.isPortrait ? image.bottom : header.bottom topMargin: page.isPortrait ? Theme.paddingLarge : 0 // in landscape, anchor the column's left side to the right of the image with a // margin of Theme.paddingLarge between them left: page.isPortrait ? parent.left : image.right leftMargin: page.isPortrait ? Theme.horizontalPageMargin : Theme.paddingLarge right: parent.right rightMargin: Theme.horizontalPageMargin } // rest of column code... }
It's also possible to customize the animation that is run when a page transitions between orientations, by changing orientationTransitions. This could be used, for example, to animate the positions of the items on the page as they move within the layout.
There are also additional properties in ApplicationWindow for handling device orientation changes; see its reference documentation for more details.
Application lifecycle
Application states
Sailfish OS is a true multiprocessing system. Applications can be moved into the background by the user. To support these different modes, applications have two states:
- Active: the app consumes all available screen real estate
- Background: the app is represented by its cover, displayed in the home screen
An application can determine its state using the Qt.application.state
property. This property is Qt.ApplicationActive
when the application is running in the foreground (Active) and Qt.ApplicationInactive
when the application is running in the background.
An application running in the background must minimize resource usage. All animations must be paused and, if possible, the app should release unused resources:
Label { // spinning label text: "Hello world!" anchors.centerIn: parent RotationAnimation on rotation { from: 0 to: 360 duration: 2000 loops: Animation.Infinite running: Qt.application.state == Qt.ApplicationActive // but only when active } }
Application covers
An application's cover is a representation of the application that is displayed in the home screen when the app is backgrounded. It is created using the Cover type and is specified with the ApplicationWindow cover property. Covers allow the user to view significant information from your app or interact with a limited subset of its features while it is backgrounded.
For example, here is an app that shows a ColorPicker. If a color is clicked, then when the app is backgrounded, the selected color is shown in the cover:
import QtQuick 2.2 import Sailfish.Silica 1.0 ApplicationWindow { id: appWindow property color selectedColor initialPage: Component { Page { ColorPicker { onColorClicked: appWindow.selectedColor = color } } } cover: Component { Cover { Rectangle { anchors.fill: parent color: appWindow.selectedColor } } } }
Note that as the cover is active while the app is backgrounded, any animations or resource-intensive processes in the cover should only be run while the cover status is Cover.Active
.
(Note that while the above example sets the ApplicationWindow::cover property as a Component, it is preferable to place the cover in a separate QML file and set the cover
property using the file URL to avoid the cost of compiling the QML component on application start-up.)
Cover actions
Cover actions provide instant access to common actions from the application cover. For instance, a music player app might provide controls for the user to pause or go to the next song from the application cover.
Cover actions are added by creating a CoverActionList with CoverAction children within a Cover. Each CoverAction sets an iconSource specifying the action button's icon, and an onTriggered signal handler that executes the action. For the color picker in the previous example, this could be used to allow the color to be reset to white from the app cover:
ApplicationWindow { cover: Component { Cover { id: appCover CoverActionList { CoverAction { iconSource: "icon.png" onTriggered: appWindow.selectedColor = "white" } } // rest of cover code... } } }
Application shutdown
The user may close the application at any time. Cleanup may be performed in the Component.onDestruction handler of ApplicationWindow.
Introduction to Sailfish Silica Scaling Sailfish Application UIs