summaryrefslogtreecommitdiffstats
path: root/src/controls/StackView.qml
diff options
context:
space:
mode:
authorRichard Moe Gustavsen <richard.gustavsen@digia.com>2013-03-25 14:45:41 +0100
committerThe Qt Project <gerrit-noreply@qt-project.org>2013-03-28 21:16:07 +0100
commit0afc935badafe8f3a0baa17a7109d65f8e076fae (patch)
tree174b60eca240be9debe14b473ba7d1d5d57ebd60 /src/controls/StackView.qml
parent36f6ef354dd395c73494d4535bc36012659d73aa (diff)
PageStack: rename PageStack to StackView
Change-Id: Idd859547ca7c7a87cfafc882bf7593246efc58e7 Reviewed-by: Jens Bache-Wiig <jens.bache-wiig@digia.com>
Diffstat (limited to 'src/controls/StackView.qml')
-rw-r--r--src/controls/StackView.qml969
1 files changed, 969 insertions, 0 deletions
diff --git a/src/controls/StackView.qml b/src/controls/StackView.qml
new file mode 100644
index 000000000..fbc1f5ec4
--- /dev/null
+++ b/src/controls/StackView.qml
@@ -0,0 +1,969 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the Qt Quick Controls module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
+** of its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.1
+import QtQuick.Controls 1.0
+import QtQuick.Controls.Private 1.0
+import "Private/StackView.js" as JSArray
+
+/*!
+ \qmltype StackView
+ \inherits Item
+ \ingroup views
+ \inqmlmodule QtQuick.Controls 1.0
+
+ \brief Provides a stack-based navigation model.
+
+ StackView implements a stack-based navigation model for an application.
+ A stack-based navigation model means that "pages" (discrete views of information)
+ are pushed onto a stack as the user navigates
+ deeper into the application page hierarchy. Similarily, the user can return back to
+ previous pages at a later point, which from a stack point of view means popping pages from the
+ top of the stack and re-activating them (make them visible on screen).
+ The \l Stack attached property provides information about items pushed onto a stack.
+
+ \section1 Using StackView in an Application
+ Using the StackView in the application is typically a simple matter of adding
+ the StackView as a child to e.g the applications top-level
+ \l{http://doc.qt.nokia.com/latest/qml-item.html} {Item}. The stack is usually
+ anchored to the edges of the window, except at the top or bottom where it might
+ be anchored to a status bar, or some other similar UI component.
+ The stack can then be used by invoking its navigation methods. The first page
+ to show in the StackView is commonly loaded assigning it to \l initialPage.
+
+ \section1 Basic Navigation
+ There are three primary navigation operations in StackView: push(), pop() and
+ replace (you replace by specifying argument \c replace to push()).
+ These correspond to classic stack operations where "push" adds a page to the
+ top of a stack, "pop" removes the top page from the stack, and "replace" is like a
+ pop followed by a push in that it replaces the topmost page on the stack with
+ a new page (but the applied transtition might be different). The topmost page
+ in the stack corresponds to the one that is \l{StackView::currentPage} {current},
+ i.e. the one that is visible on
+ screen. That means that "push" is the logical equivalent of navigating forward or
+ deeper into the application, "pop" is the equivalent of navigation back and
+ "replace" is the equivalent of replacing the current page with a different page.
+
+ Sometimes it is necessary to go back more than a single step in the stack, e.g.
+ to return to a "main" page or some kind of section page in the application.
+ For this use case, pop() can be provided with a page to pop to. This is called
+ an "unwind" operation as the stack gets unwound to the specified page. If the
+ page is not found then the stack unwinds until there is only a single page in
+ the stack, which becomes the current page. To explicitly unwind to the bottom
+ of the stack it is recommended to use \l{pop()} {pop(null)}, though technically any
+ non-existent page will do.
+
+ Given the stack [A, B, C]:
+
+ \list
+ \li \l{push()}{push(D)} => [A, B, C, D] - "push" transition animation between C and D
+ \li pop() => [A, B] - "pop" transition animation between C and B
+ \li \l{push()}{push(D, replace)} => [A, B, D] - "replace" transition between C and D
+ \li \l{pop()}{pop(A)} => [A] - "pop" transition between C and A
+ \endlist
+
+ Note that when the stack is empty, a push() will not perform a
+ transition animation because there is nothing to transition from (which will
+ typically happend during application start-up). A pop() on a stack with
+ depth 1 or 0 is a no-operation. If removing all pages from the stack is
+ needed, a separate function clear() is available.
+
+ Calling push() returns the page that was pushed onto the stack.
+ Calling pop() returns the page that was popped off the stack. When pop() is
+ called in an unwind operation the top-most page (the first page that was
+ popped, which will also be the one transitioning out) is returned.
+
+ \section1 Deep Linking
+ Deep linking means launching an application into a particular state. For example
+ a Newspaper application could be launched into showing a particular article,
+ bypassing the front page (and possible a section page) that would normally have
+ to be navigated through to get to the article in question. In terms of page
+ stacks deep linking means the ability to modify the state of the stack so that
+ you e.g. push a set of pages to the top of the stack, or that you completely reset
+ the stack to a given state.
+
+ The API for deep linking in StackView is the same as for basic navigation. If
+ you push an array instead of a single page then all the pages in that array will
+ be pushed onto the stack. The transition animation, however, will be conducted as
+ if only the last page in the array was pushed onto the stack. The normal semantics
+ of push() apply for deep linking, meaning that push() adds whatever you push onto
+ the stack. Note also that only the last item in the array will actually be loaded
+ (in case of a \l{http://doc.qt.nokia.com/latest/qml-url.html}{URL} or
+ \l{http://doc.qt.nokia.com/latest/qml-component.html}{Component}).
+ The rest will be lazy loaded as needed when entering
+ the screen upon subsequent calls to pop (or when requesting the page by using \a get).
+
+ This gives us the following result, given the stack [A, B, C]:
+
+ \list
+ \li \l{push()}{push([D, E, F])} => [A, B, C, D, E, F] - "push" transition animation between C and F
+ \li \l{push()}{push([D, E, F], replace)} => [A, B, D, E, F] - "replace" transition animation between C and F
+ \li clear(); \l{push()}{push([D, E, F])} => [D, E, F] - no transition animation (since the stack was empty)
+ \endlist
+
+ \section1 Pushing pages
+
+ A page you push onto the StackView can be either a \l Page, a \l{http://doc.qt.nokia.com/latest/qml-url.html}{URL}, a
+ string with a URL, an \l{http://doc.qt.nokia.com/latest/qml-item.html}{Item}, or a
+ \l{http://doc.qt.nokia.com/latest/qml-component.html}{Component}. To push it, you assign it
+ to a property "page" inside a property list, and send it as argument to \l{StackView::push}{push}:
+
+ \code
+ pageStack.push({page: yourPage})
+ \endcode
+
+ The list can contain several properties that controls how the page should be pushed:
+ \list
+ \li \c page: This property is required, and holds the page you want to push.
+ \li \c properties: You can set a property list of QML properties that should be assigned
+ to the page upon push. These properties will be copied into the page at the
+ time the page is loaded, or about to become the current page (normally upon push).
+ \li \c immediate: Set this property to \c true to skip transition effects. When pushing
+ an array, you only need to set this property on the first element to make the
+ whole operation immediate.
+ \li \c replace: Set this property to replace the current page on the stack. When pushing
+ an array, you only need to set this property on the first element to replace
+ as many elements on the stack as inside the array.
+ \li \c destroyOnPop: Set this property to be explicit to whether or not StackView should
+ destroy the page when its popped off the stack. By default (if \a destroyOnPop is
+ not specified), StackView will destroy pages pushed as components or URLs. Pages
+ not destroyed will be reparented back to the original parents they had before being
+ pushed onto the stack, and hidden. If you need to set this property, do it with
+ care, so that pages are not leaked.
+ \endlist
+
+ If the only argument needed is "page", you can also, as a short-hand
+ notation, do:
+
+ \code
+ pageStack.push(yourPage).
+ \endcode
+
+ You can push several pages in one go by using an array of property lists. This is
+ optimizing compared to pushing pages one by one, since StackView then can load only the
+ last page in the list. The rest will be loaded as they are about to become
+ the current page (which happends when the stack is popped). The following example shows how
+ to push an array of pages:
+
+ \code
+ pageStack.push([{page: yourPage1}, {page: yourPage2}])
+ \endcode
+
+ If inline pages/items are pushed, the page gets re-parented into an internal
+ container in the StackView. When the page is later popped off, it gets
+ re-parented back to its original owner. If, however, a page is pushed
+ as a component or a URL, the actual page will be created as a page from that component. This
+ happens automatically when the page is about to become the current page in the stack. Ownership
+ over the item will then normally be taken by the StackView. It will as such automatically
+ destroy the page when it is later popped off. The component that declared the page, by
+ contrast, remains in the ownership of the application and is not destroyed by the page stack.
+ You can override this behavior if needed by explicitly setting "destroyOnPop" in the list
+ argument given to push.
+
+ If you specify the \c properties property to push, these properties will be copied into
+ the page at the time the page is loaded (in case of a component or URL), or instead when
+ its about to become the current page (in case of an inline item). This normally happends when
+ the page is pushed. The following example shows how this can be done:
+
+ \code
+ pageStack.push({page: examplePage, properties: {fgcolor: "red", bgcolor: "blue"}});
+ \endcode
+
+ Note that if a page is declared in an item that is destroyed - even if a component
+ was used - then that page also gets destroyed.
+ This follows normal Qt parent-child destruction rules but sometimes comes as a surprise
+ for developers. In practice this means that if you declare a page B as a child of
+ page A and then do a replace from page A to page B, then page B will be destroyed when
+ page A was destroyed (as it was popped off the stack) and the application will effectively
+ be switching to a page that has been destroyed.
+
+ \section1 Lifecycle
+ The page lifecycle goes from instantiation to inactive, activating, active, deactivating,
+ inactive, and when no longer needed, destruction.
+ It can move any number of times between inactive and active. When a page is activated,
+ it's visible on the screen and is considered to be the current item. A page
+ in a page stack that is not visible is not activated, even if the page is currently the
+ top-most page in the stack. When the stack becomes visible the page that is top-most gets
+ activated. Likewise if the page stack is then hidden the top-most page would be deactivated.
+ Popping the page off the top of the stack at this point would not result in further
+ deactivation since the page is not active.
+
+ There is an attached \l{Stack::status}{Stack.status} property that tracks the lifecycle. The value of status is
+ an enumeration with values \c Stack.Inactive, \c Stack.Activating, \c Stack.Active
+ and \c Stack.Deactivating. Combined with the normal \c Component.onComplete and
+ \c Component.onDestruction signals the entire lifecycle is thus:
+
+ \list
+ \li Created: Component.onCompleted()
+ \li Activating: Stack.onStatusChanged (Stack.status is Stack.Activating)
+ \li Acivated: Stack.onStatusChanged (Stack.status is Stack.Active)
+ \li Deactivating: Stack.onStatusChanged (Stack.status is Stack.Deactivating)
+ \li Deactivated: Stack.onStatusChanged (Stack.status is Stack.Inactive)
+ \li Destruction: Component.onDestruction()
+ \endlist
+
+ \section1 Finding Pages
+ Sometimes it is necessary to search for a page, e.g. in order to unwind the stack to
+ a page to which the application does not have a reference. This is facilitated using a
+ function find() in the page stack. The find() function takes a callback function as its
+ only argument. The callback gets invoked for each page in the stack (starting at the top).
+ If the callback returns true then it signals that a match has been found and the find()
+ function returns that page. If the callback fails to return true (i.e. no match is found)
+ then find() returns \c null.
+
+ The code below searches for a page in the stack that has a name "foo" and then unwinds to
+ that page. Note that since find() returns null if no page is found and since pop unwinds to
+ the bottom of the stack if null is given as the target page, the code works well even in the
+ case that no matching page was found.
+
+ \code
+ pageStack.pop(pageStack.find(function(page) {
+ return page.name == "foo";
+ }));
+ \endcode
+
+ You can also get to a page in the page stack using get(index). You should use
+ this function if your page depends on another page in the stack, as the function will
+ ensure that the page at the given index gets loaded before it is returned.
+
+ \code
+ previousPage = pageStack.get(myPage.index - 1));
+ \endcode
+
+ \section1 Transitions
+
+ A transition is performed whenever a page is pushed or popped, and consists of
+ two pages: enterPage and exitPage. The pagestack itself will never move pages
+ around, but instead delegate the job to an external animation set by the style
+ or the application developer. How pages should visually enter and leave the stack
+ is therefore completely controlled from the outside.
+
+ When the transition starts, the pagestack will search for a transition that
+ matches the operation executed. There are three transitions to choose
+ from: pushTransition, popTransition, and replaceTransition. Each implements how
+ enterPage should animate in, and exitPage out. The transitions are
+ collected inside a StackViewDelegate object assigned to
+ \l {StackView::delegate}{delegate}. By default, popTransition and
+ replaceTransition will be the same as pushTransition, unless you set them
+ to something else.
+
+ A simple fade transition could be implemented as:
+
+ \qml
+ StackView {
+ delegate: StackViewDelegate {
+ function transitionFinished(properties)
+ {
+ properties.exitPage.opacity = 1
+ }
+
+ property Component pushTransition: StackViewTransition {
+ PropertyAnimation {
+ target: enterPage
+ property: "opacity"
+ from: 0
+ to: 1
+ }
+ PropertyAnimation {
+ target: exitPage
+ property: "opacity"
+ from: 1
+ to: 0
+ }
+ }
+ }
+ }
+ \endqml
+
+ PushTransition needs to inherit from StackViewTransition, which is a ParallelAnimation that
+ contains the properties \c enterPage and \c exitPage. You set the target of your
+ inner animations to those pages. Since the same page instance can be pushed several
+ times to a pagestack, and since pages also can override transitions, your StackViewDelegate
+ always need to override
+ \l {StackViewDelegate::transitionFinished(properties)}{StackViewDelegate.transitionFinished(properties)}.
+ Implement this function to reset any properties animated on the exitPage so that later
+ transitions can expect the pages to be in a default state.
+
+ A more complex example could look like the following. Here, the pages slides in lying on the side before
+ they are rotated up in an upright position:
+
+ \qml
+ StackView {
+ delegate: StackViewDelegate {
+ function transitionFinished(properties)
+ {
+ properties.exitPage.x = 0
+ properties.exitPage.rotation = 0
+ }
+
+ property Component pushTransition: StackViewTransition {
+ SequentialAnimation {
+ ScriptAction {
+ script: enterPage.rotation = 90
+ }
+ PropertyAnimation {
+ target: enterPage
+ property: "x"
+ from: enterPage.width
+ to: 0
+ }
+ PropertyAnimation {
+ target: enterPage
+ property: "rotation"
+ from: 90
+ to: 0
+ }
+ }
+ PropertyAnimation {
+ target: exitPage
+ property: "x"
+ from: 0
+ to: -exitPage.width
+ }
+ }
+ }
+ }
+ \endqml
+
+ \section2 Advanced usage
+
+ When the StackView needs a new transition, it first calls
+ \l {StackViewDelegate::getTransition(properties)}{StackViewDelegate.getTransition(properties)}.
+ The base implementation of this function just looks for a property named \c properties.name inside
+ itself (root), which is how it finds \c {property Component pushTransition} in the examples above.
+
+ \code
+ function getTransition(properties)
+ {
+ return root[properties.name]
+ }
+ \endcode
+
+ You can override this function for your delegate if you need extra logic to decide which
+ transition to return. You could for example introspect the pages, and return different animations
+ depending on the their internal state. StackView will expect you to return a Component that
+ contains a StackViewTransition, or a StackViewTransition directly. The former is easier, as StackView will
+ then create the transition and later destroy it when it's done, while avoiding any sideeffects
+ caused by the transition being alive long after it ran. Returning a StackViewTransition directly
+ can be useful if you need to write some sort of transition caching for performance reasons.
+ As an optimization, you can also return \c null to signal that you just want to show/hide the pages
+ immediately without creating or running any transitions. You can also override this function if
+ you need to alter the pages in any way before the transition starts.
+
+ \c properties contains the properties that will be assigned to the StackViewTransition before
+ it runs. In fact, you can add more properties to this object during the call
+ if you need to initialize additional properties of your custom StackViewTransition when the returned
+ component is instanciated.
+
+ The following example shows how you can decide run-time which animation to use:
+
+ \qml
+ StackViewDelegate {
+ function getTransition(properties)
+ {
+ return (properties.enterPage.Stack.index % 2) ? horizontalTransition : verticalTransition
+ }
+
+ function transitionFinished(properties)
+ {
+ properties.exitPage.x = 0
+ properties.exitPage.y = 0
+ }
+
+ property Component horizontalTransition: StackViewTransition {
+ PropertyAnimation {
+ target: enterPage
+ property: "x"
+ from: target.width
+ to: 0
+ duration: 300
+ }
+ PropertyAnimation {
+ target: exitPage
+ property: "x"
+ from: 0
+ to: target.width
+ duration: 300
+ }
+ }
+
+ property Component verticalTransition: StackViewTransition {
+ PropertyAnimation {
+ target: enterPage
+ property: "y"
+ from: target.height
+ to: 0
+ duration: 300
+ }
+ PropertyAnimation {
+ target: exitPage
+ property: "y"
+ from: 0
+ to: target.height
+ duration: 300
+ }
+ }
+ }
+ \endqml
+*/
+
+Item {
+ id: root
+
+ /*! \qmlproperty int StackView::depth
+ \readonly
+ The number of pages currently pushed onto the stack.
+ */
+ readonly property alias depth: root.__depth
+
+ /*! \qmlproperty Item StackView::currentPage
+ \readonly
+ The currently top-most page in the stack.
+ */
+ readonly property alias currentPage: root.__currentPage
+
+ /*! The first \l Page that should be shown when the StackView is created.
+ \a initialPage can take same value as the first argument to \l{StackView::push()}
+ {StackView.push()}. Note that this is just a convenience for writing
+ \c{Component.onCompleted: pageStack.push(myInitialPage)}
+
+ Examples:
+
+ \list
+ \li initialPage: Qt.resolvedUrl("MyPage.qml")
+ \li initialPage: myItem
+ \li initialPage: {"page" : Qt.resolvedUrl("MyPage.qml"), "properties" : {"color" : "red"}}
+ \endlist
+ \sa push
+ */
+ property var initialPage: null
+
+ /*! \readonly
+ \a busy is \c true if a page transition is running, and \c false otherwise. */
+ readonly property bool busy: __currentTransition !== null
+
+ /*! The transitions to use when pushing or popping items.
+ For better understanding on how to apply custom transitions, read \l{Transitions}.
+ \sa {Stack::transitions}{Stack.transitions} */
+ property StackViewDelegate delegate: StackViewSlideDelegate {}
+
+ /*! Pushes a page onto the stack. The function takes a property list as argument, which
+ should contain one or more of the following properties:
+ \list
+ \li \c page: This property is required, and holds the page you want to push.
+ It can be a \l Page, a \l{http://doc.qt.nokia.com/latest/qml-url.html}{URL}, a string with a
+ URL, an \l{http://doc.qt.nokia.com/latest/qml-item.html}{Item}, a
+ \l{http://doc.qt.nokia.com/latest/qml-component.html}{Component}.
+ \li \c properties: You can set a property list of QML properties that should be assigned
+ to the page upon push. These properties will be copied into the page at the
+ time the page is loaded (in case of a component or URL), or else the first time it
+ becomes the current page (normally upon push).
+ \li \c immediate: Set this property to \c true to skip transition effects. When pushing
+ an array, you only need to set this property on the first element to make the
+ whole operation immediate.
+ \li \c replace: Set this property to replace the current page on the stack. When pushing
+ an array, you only need to set this property on the first element to replace
+ as many elements on the stack as inside the array.
+ \li \c destroyOnPop: Set this property to be explicit to whether or not StackView should
+ destroy the page when its popped off the stack. By default (if \a destroyOnPop is
+ not specified), StackView will destroy pages pushed as components or URLs. Pages
+ not destroyed will be reparented back to the original parents they had before being
+ pushed onto the stack, and hidden. If you need to set this property, do it with
+ care, so that pages are not leaked.
+ \endlist
+
+ You can also push an array of pages (property lists) if you need to push several pages
+ in one go. A transition will then only occur between the current page and the last
+ page in the list. The other pages will be deferred loaded until needed.
+
+ Examples:
+ \list
+ \li pageStack.push({page:aPage})
+ \li pageStack.push({page:aURL, immediate: true, replace: true})
+ \li pageStack.push({page:aRectangle, properties:{color:"red"}})
+ \li pageStack.push({page:aComponent, properties:{color:"red"}})
+ \li pageStack.push({page:aComponent.createObject(), destroyOnPop:true})
+ \li pageStack.push([{page:aPage, immediate:true}, {page:aURL}])
+ \endlist
+
+ Note: If the only argument needed is "page", you can also, as a short-hand
+ notation, do: \c{pageStack.push(aPage)}.
+
+ Returns the page that became current.
+
+ \sa initialPage
+ \sa {Pushing pages}
+ */
+ function push(page) {
+ // Note: we support two different APIs in this function; The old meego API, and
+ // the new "property list" API. Hence the reason for hiding the fact that you
+ // can pass more arguments than shown in the signature:
+ if (__recursionGuard(true))
+ return
+ var properties = arguments[1]
+ var immediate = arguments[2]
+ var replace = arguments[3]
+ var arrayPushed = (page instanceof Array)
+ var firstPage = arrayPushed ? page[0] : page
+ immediate = (immediate || JSArray.stackView.length === 0)
+
+ if (firstPage && firstPage.page && firstPage.hasOwnProperty("x") === false) {
+ // Property list API used:
+ immediate = immediate || firstPage.immediate
+ replace = replace || firstPage.replace
+ }
+
+ // Create, and push, a new javascript object, called "element", onto the stack.
+ // This element contains all the information necessary to construct the page, and
+ // will, after loaded, also contain the loaded page:
+ if (arrayPushed) {
+ if (page.length === 0)
+ return
+ var outElement = replace ? JSArray.pop() : JSArray.current()
+ for (var i=0; i<page.length; ++i)
+ JSArray.push({"pageComponent" : page[i], loaded: false, index: __depth, properties: properties});
+ } else {
+ outElement = replace ? JSArray.pop() : JSArray.current()
+ JSArray.push({"pageComponent" : page, loaded: false, index: __depth, properties: properties})
+ }
+
+ var currentElement = JSArray.current()
+ var transition = {
+ inElement: currentElement,
+ outElement: outElement,
+ transitionElement: currentElement,
+ immediate: immediate,
+ replace: replace,
+ push: true
+ }
+ __performPageTransition(transition)
+ __recursionGuard(false)
+ return __currentPage
+ }
+
+ /*! Pops one or more pages off the stack. The function takes a property list as argument
+ which can contain one or more of the following properties:
+ \list
+ \li \c page: If specified, all pages down to (but not including) \a page will be
+ popped off. if \a page is \c null, all pages down to (but not including) the
+ first page will be popped. If not specified, only the current page will be
+ popped.
+ \li \c immediate: Set this property to \c true to skip transition effects.
+ \endlist
+
+ Examples:
+ \list
+ \li pageStack.pop()
+ \li pageStack.pop({page:somePage, immediate: true})
+ \li pageStack.pop({immediate: true})
+ \li pageStack.pop(null)
+ \endlist
+
+ Note: If the only argument needed is "page", you can also, as a short-hand
+ notation, do: \c{pageStack.pop(aPage)}.
+
+ Returns the page that was popped off
+ \sa clear()
+ */
+ function pop(page) {
+ if (__depth <= 1)
+ return null
+ if (page && page.hasOwnProperty("x") === false) {
+ // Property list API used:
+ var immediate = (page.immediate === true)
+ page = page.page
+ } else {
+ immediate = (arguments[1] === true)
+ }
+
+ if (page === __currentPage)
+ return
+
+ if (__recursionGuard(true))
+ return
+
+ var outElement = JSArray.pop()
+ var transitionElement = outElement
+ var inElement = JSArray.current()
+
+ if (__depth > 1 && page !== undefined && page !== inElement.page) {
+ // Pop from the top until we find 'page', and return the corresponding
+ // element. Skip all non-loaded pages (except the first), since no one
+ // has any references to such pages anyway:
+ while (__depth > 1 && !JSArray.current().loaded)
+ JSArray.pop()
+ inElement = JSArray.current()
+ while (__depth > 1 && page !== inElement.page) {
+ JSArray.pop()
+ __cleanup(inElement)
+ while (__depth > 1 && !JSArray.current().loaded)
+ JSArray.pop()
+ inElement = JSArray.current()
+ }
+ }
+
+ var transition = {
+ inElement: inElement,
+ outElement: outElement,
+ transitionElement: transitionElement,
+ immediate: immediate,
+ replace: false,
+ push: false
+ }
+ __performPageTransition(transition)
+ __recursionGuard(false)
+ return outElement.page;
+ }
+
+ /*! Remove all pages from the stack. No animations will be applied. */
+ function clear() {
+ if (__recursionGuard(true))
+ return
+ if (__currentTransition)
+ __currentTransition.animation.complete()
+ __currentPage = null
+ var count = __depth
+ for (var i=0; i<count; ++i) {
+ var element = JSArray.pop()
+ if (element.page)
+ __cleanup(element);
+ }
+ __recursionGuard(false)
+ }
+
+ /*! Search for a specific page inside the stack. \a func will
+ be called for each page in the stack (with the page as argument)
+ until the function returns true. Return value will be the page found. E.g:
+ find(function(page, index) { return page.isTheOne })
+ Set \a onlySearchLoadedPages to \c true to not load pages that are
+ not loaded into memory */
+ function find(func, onlySearchLoadedPages) {
+ for (var i=__depth-1; i>=0; --i) {
+ var element = JSArray.stackView[i];
+ if (onlySearchLoadedPages !== true)
+ __loadElement(element)
+ else if (!element.page)
+ continue
+ if (func(element.page))
+ return element.page
+ }
+ return null;
+ }
+
+ /*! Returns the page at position \a index in
+ the page stack. If \a dontLoad is true, the
+ page will not be forced to load (and \c null
+ will be returned if not yet loaded) */
+ function get(index, dontLoad)
+ {
+ if (index < 0 || index >= JSArray.stackView.length)
+ return null
+ var element = JSArray.stackView[index]
+ if (dontLoad !== true) {
+ __loadElement(element)
+ return element.page
+ } else if (element.page) {
+ return element.page
+ } else {
+ return null
+ }
+ }
+
+ /*! Immediately completes any ongoing transition.
+ /sa Animation.complete
+ */
+ function completeTransition()
+ {
+ if (__recursionGuard(true))
+ return
+ if (__currentTransition)
+ __currentTransition.animation.complete()
+ __recursionGuard(false)
+ }
+
+ /********* DEPRECATED API *********/
+
+ /*! \internal
+ \deprecated Use Push() instead */
+ function replace(page, properties, immediate) {
+ push(page, properties, immediate, true)
+ }
+
+ /********* PRIVATE API *********/
+
+ width: parent ? parent.width : 0
+ height: parent ? parent.height : 0
+
+ /*! \internal The currently top-most page in the stack. */
+ property Item __currentPage: null
+ /*! \internal The number of pages currently pushed onto the stack. */
+ property int __depth: 0
+ /*! \internal Stores the transition info while a transition is ongoing */
+ property var __currentTransition: null
+ /*! \internal Stops the user from pushing pages while preparing a transition */
+ property bool __guard: false
+
+ /*! \internal */
+ Component.onCompleted: {
+ if (initialPage)
+ push(initialPage)
+ }
+
+ /*! \internal */
+ Component.onDestruction: {
+ if (__currentTransition)
+ __currentTransition.animation.complete()
+ __currentPage = null
+ }
+
+ /*! \internal */
+ function __recursionGuard(use)
+ {
+ if (use && __guard) {
+ console.warn("Warning: StackView: You cannot push/pop recursively!")
+ console.trace()
+ return true
+ }
+ __guard = use
+ }
+
+ /*! \internal */
+ function __loadElement(element)
+ {
+ if (element.loaded) {
+ if (!element.page) {
+ element.page = invalidPageReplacement.createObject(root)
+ element.page.text = "\nError: The page has been deleted outside StackView!"
+ }
+ return
+ }
+ if (!element.pageComponent) {
+ element.page = invalidPageReplacement.createObject(root)
+ element.page.text = "\nError: Invalid page (page was 'null'). "
+ + "This might indicate that the page was deleted outside StackView!"
+ return
+ }
+
+ var comp = __resolvePageComponent(element.pageComponent, element)
+
+ // Assign properties to Page:
+ if (!element.properties)
+ element.properties = {}
+ element.properties.__index = element.index
+ element.properties.__stackView = root
+
+ if (comp.hasOwnProperty("createObject")) {
+ if (comp.status === Component.Error) {
+ element.page = invalidPageReplacement.createObject(root)
+ element.page.text = "\nError: Could not load: " + comp.errorString()
+ } else {
+ element.page = comp.createObject(root, element.properties)
+ // Destroy pages we create unless the user specified something else:
+ if (!element.hasOwnProperty("destroyOnPop"))
+ element.destroyOnPop = true
+ }
+ } else {
+ // comp is already an Item, so just reparent it into the pagestack:
+ element.page = comp
+ element.originalParent = parent
+ element.page.parent = root
+ for (var prop in element.properties) {
+ if (element.page.hasOwnProperty(prop))
+ element.page[prop] = element.properties[prop];
+ }
+ // Do not destroy pages we didn't create, unless the user specified something else:
+ if (!element.hasOwnProperty("destroyOnPop"))
+ element.destroyOnPop = false
+ }
+
+ // Let page fill all available space by default:
+ element.page.width = Qt.binding(function() { return root.width })
+ element.page.height = Qt.binding(function() { return root.height })
+
+ delete element.properties.__index
+ delete element.properties.__stackView
+ element.loaded = true
+ }
+
+ /*! \internal */
+ function __resolvePageComponent(unknownObjectType, element)
+ {
+ // We need this extra resolve function since we dont really
+ // know what kind of object the user pushed. So we try to
+ // figure it out by inspecting the object:
+ if (unknownObjectType.hasOwnProperty("createObject")) {
+ return unknownObjectType
+ } else if (typeof unknownObjectType == "string") {
+ return Qt.createComponent(unknownObjectType)
+ } else if (unknownObjectType.hasOwnProperty("x")) {
+ return unknownObjectType
+ } else if (unknownObjectType.hasOwnProperty("page")) {
+ // INVARIANT: user pushed a JS-object
+ element.properties = unknownObjectType.properties
+ if (!unknownObjectType.page)
+ unknownObjectType.page = invalidPageReplacement
+ if (unknownObjectType.hasOwnProperty("destroyOnPop"))
+ element.destroyOnPop = unknownObjectType.destroyOnPop
+ return __resolvePageComponent(unknownObjectType.page, element)
+ } else {
+ // We cannot determine the type, so assume its a URL:
+ return Qt.createComponent(unknownObjectType)
+ }
+ }
+
+ /*! \internal */
+ function __cleanup(element) {
+ // INVARIANT: element has been removed from JSArray. Destroy its
+ // page, or reparent it back to the parent it had before it was pushed:
+ var page = element.page
+ if (element.destroyOnPop) {
+ page.destroy()
+ } else {
+ // Mark the page as no longer part of the StackView. It
+ // might reenter on pop if pushed several times:
+ page.visible = false
+ __setPageStatus(page, Stack.Inactive)
+ page.Stack.__stackView = null
+ page.Stack.__index = -1
+ if (element.originalParent)
+ page.parent = element.originalParent
+ }
+ }
+
+ /*! \internal */
+ function __setPageStatus(page, status) {
+ page.Stack.__status = status
+ }
+
+ /*! \internal */
+ function __performPageTransition(transition)
+ {
+ // Animate page in "outElement" out, and page in "inElement" in. Set a guard to protect
+ // the user from pushing new pages on signals that will fire while preparing for the transition
+ // (e.g Stack.onCompleted, Stack.onStatusChanged, Stack.onIndexChanged etc). Otherwise, we will enter
+ // this function several times, which causes the pages to be half-way updated.
+ if (__currentTransition)
+ __currentTransition.animation.complete()
+ __loadElement(transition.inElement)
+
+ transition.name = transition.replace ? "replaceTransition" : (transition.push ? "pushTransition" : "popTransition")
+ var enterPage = transition.inElement.page
+ transition.enterPage = enterPage
+
+ // Since a page can be pushed several times, we need to update its properties:
+ enterPage.parent = root
+ enterPage.Stack.__stackView = root
+ enterPage.Stack.__index = transition.inElement.index
+ __currentPage = enterPage
+
+ if (!transition.outElement) {
+ // A transition consists of two pages, but we got just one. So just show the page:
+ enterPage.visible = true
+ __setPageStatus(enterPage, Stack.Activating)
+ __setPageStatus(enterPage, Stack.Active)
+ return
+ }
+
+ var exitPage = transition.outElement.page
+ transition.exitPage = exitPage
+ if (enterPage === exitPage)
+ return
+
+ if (root.delegate) {
+ transition.properties = {
+ "name":transition.name,
+ "enterPage":transition.enterPage,
+ "exitPage":transition.exitPage,
+ "immediate":transition.immediate }
+ var anim = root.delegate.getTransition(transition.properties)
+ if (anim.createObject) {
+ anim = anim.createObject(null, transition.properties)
+ anim.runningChanged.connect(function(){ if (anim.running === false) anim.destroy() })
+ }
+ transition.animation = anim
+ }
+
+ if (!transition.animation) {
+ console.warn("Warning: StackView: no", transition.name, "found!")
+ return
+ }
+ if (enterPage.anchors.fill || exitPage.anchors.fill)
+ console.warn("Warning: StackView: cannot transition a page that is anchored!")
+
+ __currentTransition = transition
+ __setPageStatus(exitPage, Stack.Deactivating)
+ enterPage.visible = true
+ __setPageStatus(enterPage, Stack.Activating)
+ transition.animation.runningChanged.connect(animationFinished)
+ transition.animation.start()
+ // NB! For empty animations, "animationFinished" is already
+ // executed at this point, leaving __animation === null:
+ if (transition.immediate === true && transition.animation)
+ transition.animation.complete()
+ }
+
+ /*! \internal */
+ function animationFinished()
+ {
+ if (!__currentTransition || __currentTransition.animation.running)
+ return
+
+ __currentTransition.animation.runningChanged.disconnect(animationFinished)
+ __currentTransition.exitPage.visible = false
+ __setPageStatus(__currentTransition.exitPage, Stack.Inactive);
+ __setPageStatus(__currentTransition.enterPage, Stack.Active);
+ __currentTransition.properties.animation = __currentTransition.animation
+ root.delegate.transitionFinished(__currentTransition.properties)
+
+ if (!__currentTransition.push || __currentTransition.replace)
+ __cleanup(__currentTransition.outElement)
+
+ __currentTransition = null
+ }
+
+ /*! \internal */
+ property Component invalidPageReplacement: Component {
+ Text {
+ width: parent.width
+ height: parent.height
+ wrapMode: Text.WrapAtWordBoundaryOrAnywhere
+ }
+ }
+}