mirror of
				https://github.com/subsurface/subsurface.git
				synced 2025-02-19 22:16:15 +00:00 
			
		
		
		
	- Fixes interactivity in context drawer. - These are mostly cleanups that have been done pre-merge of these components. This now is the state of master in plasma-mobile. - makes navigation a bit more intuitive Signed-off-by: Sebastian Kügler <sebas@kde.org>
		
			
				
	
	
		
			510 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			QML
		
	
	
	
	
	
			
		
		
	
	
			510 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			QML
		
	
	
	
	
	
| /****************************************************************************
 | |
| **
 | |
| ** Copyright (C) 2012 Marco Martin  <mart@kde.org>
 | |
| **
 | |
| ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
 | |
| ** All rights reserved.
 | |
| ** Contact: Nokia Corporation (qt-info@nokia.com)
 | |
| **
 | |
| ** This file is part of the Qt Components project.
 | |
| **
 | |
| ** $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 Nokia Corporation 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.0
 | |
| import QtQuick.Controls 1.0
 | |
| import QtQuick.Layouts 1.2
 | |
| import org.kde.plasma.mobilecomponents 0.2
 | |
| 
 | |
| import "private/PageStack.js" as Engine
 | |
| 
 | |
| Item {
 | |
|     id: actualRoot
 | |
| 
 | |
|     width: parent ? parent.width : 0
 | |
|     height: parent ? parent.height : 0
 | |
| 
 | |
| 
 | |
|     property int depth: Engine.getDepth()
 | |
|     property Item currentPage: null
 | |
|     property Item lastVisiblePage
 | |
|     property ToolBar toolBar
 | |
|     property variant initialPage
 | |
|     //A column is wide enough for 30 characters
 | |
|     property int columnWidth: Math.round(parent.width/(Units.gridUnit*30)) > 0 ? parent.width/Math.round(parent.width/(Units.gridUnit*30)) : width
 | |
|     property alias clip: scrollArea.clip
 | |
| 
 | |
|     // Indicates whether there is an ongoing page transition.
 | |
|     property bool busy: internal.ongoingTransitionCount > 0
 | |
| 
 | |
|     // Pushes a page on the stack.
 | |
|     // The page can be defined as a component, item or string.
 | |
|     // If an item is used then the page will get re-parented.
 | |
|     // If a string is used then it is interpreted as a url that is used to load a page component.
 | |
|     //
 | |
|     // The page can also be given as an array of pages. In this case all those pages will be pushed
 | |
|     // onto the stack. The items in the stack can be components, items or strings just like for single
 | |
|     // pages. Additionally an object can be used, which specifies a page and an optional properties
 | |
|     // property. This can be used to push multiple pages while still giving each of them properties.
 | |
|     // When an array is used the transition animation will only be to the last page.
 | |
|     //
 | |
|     // The properties argument is optional and allows defining a map of properties to set on the page.
 | |
|     // If the immediate argument is true then no transition animation is performed.
 | |
|     // Returns the page instance.
 | |
|     function push(page, properties, immediate)
 | |
|     {
 | |
|         pop(lastVisiblePage, true);
 | |
|         scrollAnimation.running = false;
 | |
|         var item = Engine.push(page, properties, false, immediate)
 | |
|         scrollToLevel(depth)
 | |
|         return item
 | |
|     }
 | |
| 
 | |
|     // Pops a page off the stack.
 | |
|     // If page is specified then the stack is unwound to that page, to unwind to the first page specify
 | |
|     // page as null. If the immediate argument is true then no transition animation is performed.
 | |
|     // Returns the page instance that was popped off the stack.
 | |
|     function pop(page, immediate)
 | |
|     {
 | |
|         scrollToLevel(depth-1);
 | |
|         return Engine.pop(page, immediate);
 | |
|     }
 | |
| 
 | |
|     // Replaces a page on the stack.
 | |
|     // See push() for details.
 | |
|     function replace(page, properties, immediate)
 | |
|     {
 | |
|         pop(lastVisiblePage, true);
 | |
|         scrollAnimation.running = false;
 | |
|         var item = Engine.push(page, properties, true, immediate);
 | |
|         scrollToLevel(depth)
 | |
|         return item
 | |
|     }
 | |
| 
 | |
|     // Clears the page stack.
 | |
|     function clear()
 | |
|     {
 | |
|         return Engine.clear();
 | |
|     }
 | |
| 
 | |
|     // Iterates through all pages (top to bottom) and invokes the specified function.
 | |
|     // If the specified function returns true the search stops and the find function
 | |
|     // returns the page that the iteration stopped at. If the search doesn't result
 | |
|     // in any page being found then null is returned.
 | |
|     function find(func)
 | |
|     {
 | |
|         return Engine.find(func);
 | |
|     }
 | |
| 
 | |
|     // Scroll the view to have the page of the given level as first item
 | |
|     function scrollToLevel(level)
 | |
|     {
 | |
|         if (level < 0 || level > depth || root.width < width) {
 | |
|             return
 | |
|         }
 | |
| 
 | |
|         var firstLevel = Math.max(0, level - mainFlickable.width/columnWidth + 1);
 | |
|         scrollAnimation.to = Math.max(0, Math.min(Math.max(0, columnWidth * (firstLevel - 1)), mainFlickable.contentWidth));
 | |
|         scrollAnimation.running = true;
 | |
|     }
 | |
| 
 | |
|     SequentialAnimation {
 | |
|         id: scrollAnimation
 | |
|         property alias to: actualScrollAnimation.to
 | |
|         NumberAnimation {
 | |
|             id: actualScrollAnimation
 | |
|             target: mainFlickable
 | |
|             properties: "contentX"
 | |
|             duration: internal.transitionDuration
 | |
|             easing.type: Easing.InOutQuad
 | |
|         }
 | |
|         ScriptAction {
 | |
|             script: {
 | |
|                 //At startup sometimes the contentX is NaN for an instant
 | |
|                 if (isNaN(mainFlickable.contentX)) {
 | |
|                     return;
 | |
|                 }
 | |
| 
 | |
|                 actualRoot.lastVisiblePage = root.children[Math.floor((mainFlickable.contentX + mainFlickable.width - 1)/columnWidth)].page
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // Called when the page stack visibility changes.
 | |
|     onVisibleChanged: {
 | |
|         if (currentPage) {
 | |
|             if (visible)
 | |
|                 currentPage.visible = currentPage.parent.visible = true;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     onInitialPageChanged: {
 | |
|         if (!internal.completed) {
 | |
|             return
 | |
|         }
 | |
| 
 | |
|         if (initialPage) {
 | |
|             if (depth == 0) {
 | |
|                 push(initialPage, null, true)
 | |
|             } else if (depth == 1) {
 | |
|                 replace(initialPage, null, true)
 | |
|             } else {
 | |
|                 console.log("Cannot update PageStack.initialPage")
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     onWidthChanged: {
 | |
|         var firstLevel = Math.max(0, depth - mainFlickable.width/columnWidth + 1);
 | |
|         mainFlickable.contentX = Math.max(0, Math.min(Math.max(0, columnWidth * (firstLevel - 1)), mainFlickable.contentWidth));
 | |
|     }
 | |
|     Component.onCompleted: {
 | |
|         internal.completed = true
 | |
|         if (initialPage && depth == 0)
 | |
|             push(initialPage, null, true)
 | |
|     }
 | |
| 
 | |
|     QtObject {
 | |
|         id: internal
 | |
| 
 | |
|         // The number of ongoing transitions.
 | |
|         property int ongoingTransitionCount: 0
 | |
| 
 | |
|         //FIXME: there should be a way to access to theh without storing it in an ugly way
 | |
|         property bool completed: false
 | |
| 
 | |
|         // Duration of transition animation (in ms)
 | |
|         property int transitionDuration: Units.longDuration
 | |
|     }
 | |
| 
 | |
|     ScrollView {
 | |
|         id: scrollArea
 | |
|         anchors.fill: parent
 | |
|         Flickable {
 | |
|             id: mainFlickable
 | |
|             anchors.fill: parent
 | |
|             interactive: root.width > width
 | |
|             boundsBehavior: Flickable.StopAtBounds
 | |
|             contentWidth: root.width
 | |
|             contentHeight: height
 | |
|             Row {
 | |
|                 id: root
 | |
|                 spacing: -Units.gridUnit * 8
 | |
|                 width: Math.max((depth-1+children[children.length-1].takenColumns) * columnWidth, childrenRect.width - Units.gridUnit * 8)
 | |
| 
 | |
|                 height: parent.height
 | |
|                 Behavior on width {
 | |
|                     NumberAnimation {
 | |
|                         duration: internal.transitionDuration
 | |
|                         easing.type: Easing.InOutQuad
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             onMovementEnded: {
 | |
|                 scrollToLevel(Math.round(contentX/columnWidth)+1)
 | |
|             }
 | |
|             onFlickEnded: {
 | |
|                 movementEnded();
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // Component for page containers.
 | |
|     Component {
 | |
|         id: containerComponent
 | |
| 
 | |
|         Item {
 | |
|             id: container
 | |
| 
 | |
|             implicitWidth: actualContainer.width + Units.gridUnit * 8
 | |
|             width: implicitWidth
 | |
|             height: parent ? parent.height : 0
 | |
| 
 | |
|             x: 0
 | |
| 
 | |
|             // The actual parent of page: page will anchor to that
 | |
|             property Item pageParent: actualContainer
 | |
| 
 | |
|             property int pageDepth: 0
 | |
|             Component.onCompleted: {
 | |
|                 pageDepth = Engine.getDepth() + 1
 | |
|                 container.z = -Engine.getDepth()
 | |
|             }
 | |
| 
 | |
|             // The states correspond to the different possible positions of the container.
 | |
|             state: "Hidden"
 | |
| 
 | |
|             // The page held by this container.
 | |
|             property Item page: null
 | |
| 
 | |
|             // The owner of the page.
 | |
|             property Item owner: null
 | |
| 
 | |
|             // The width of the longer stack dimension
 | |
|             property int stackWidth: Math.max(actualRoot.width, actualRoot.height)
 | |
| 
 | |
| 
 | |
|             // Flag that indicates the container should be cleaned up after the transition has ended.
 | |
|             property bool cleanupAfterTransition: false
 | |
| 
 | |
|             // Flag that indicates if page transition animation is running
 | |
|             property bool transitionAnimationRunning: false
 | |
| 
 | |
|             // State to be set after previous state change animation has finished
 | |
|             property string pendingState: "none"
 | |
| 
 | |
|             //how many columns take the page?
 | |
|             property alias takenColumns: actualContainer.takenColumns
 | |
| 
 | |
|             // Ensures that transition finish actions are executed
 | |
|             // in case the object is destroyed before reaching the
 | |
|             // end state of an ongoing transition
 | |
|             Component.onDestruction: {
 | |
|                 if (transitionAnimationRunning)
 | |
|                     transitionEnded();
 | |
|             }
 | |
| 
 | |
|             Item {
 | |
|                 id: actualContainer
 | |
| 
 | |
|                 anchors {
 | |
|                     top: parent.top
 | |
|                     bottom: parent.bottom
 | |
|                     right: parent.right
 | |
|                     rightMargin: Units.gridUnit * 8
 | |
|                 }
 | |
| 
 | |
|                 property int takenColumns: {
 | |
|                     if (container.page && container.page.Layout && container.page.Layout.fillWidth) {
 | |
|                         return Math.max(1, Math.round(actualRoot.width/columnWidth)-(container.x > 0 ? 1: 0));
 | |
|                     } else {
 | |
|                         return Math.max(1, Math.round(container.page ? container.page.implicitWidth/columnWidth : 1));
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 width: (container.pageDepth >= actualRoot.depth ? Math.min(actualRoot.width, takenColumns*columnWidth) : columnWidth)
 | |
|             }
 | |
| 
 | |
|             Rectangle {
 | |
|                 anchors {
 | |
|                     top: parent.top
 | |
|                     bottom: parent.bottom
 | |
|                     right: actualContainer.right
 | |
|                 }
 | |
|                 width: 1
 | |
|                 color: Theme.textColor
 | |
|                 opacity: 0.3
 | |
|                 visible: container.pageDepth < actualRoot.depth
 | |
|             }
 | |
| 
 | |
|             // Sets pending state as current if state change is delayed
 | |
|             onTransitionAnimationRunningChanged: {
 | |
|                 if (!transitionAnimationRunning && pendingState != "none") {
 | |
|                     state = pendingState;
 | |
|                     pendingState = "none";
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             // Handles state change depening on transition animation status
 | |
|             function setState(newState)
 | |
|             {
 | |
|                 if (transitionAnimationRunning)
 | |
|                     pendingState = newState;
 | |
|                 else
 | |
|                     state = newState;
 | |
|             }
 | |
| 
 | |
|             // Performs a push enter transition.
 | |
|             function pushEnter(immediate, orientationChanges)
 | |
|             {
 | |
|                 if (!immediate) {
 | |
|                     setState("Right");
 | |
|                 }
 | |
|                 setState("");
 | |
|                 page.visible = true;
 | |
|             }
 | |
| 
 | |
|             // Performs a push exit transition.
 | |
|             function pushExit(replace, immediate, orientationChanges)
 | |
|             {
 | |
|                 if (replace) {
 | |
|                     setState(immediate ? "Hidden" : "Left");
 | |
|                 }
 | |
| 
 | |
|                 if (replace) {
 | |
|                     if (immediate)
 | |
|                         cleanup();
 | |
|                     else
 | |
|                         cleanupAfterTransition = true;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             // Performs a pop enter transition.
 | |
|             function popEnter(immediate, orientationChanges)
 | |
|             {
 | |
|                 setState("");
 | |
|                 page.visible = true;
 | |
|             }
 | |
| 
 | |
|             // Performs a pop exit transition.
 | |
|             function popExit(immediate, orientationChanges)
 | |
|             {
 | |
|                 setState(immediate ? "Hidden" : "Left");
 | |
| 
 | |
|                 if (immediate)
 | |
|                     cleanup();
 | |
|                 else
 | |
|                     cleanupAfterTransition = true;
 | |
|             }
 | |
| 
 | |
|             // Called when a transition has started.
 | |
|             function transitionStarted()
 | |
|             {
 | |
|                 container.clip = true
 | |
|                 transitionAnimationRunning = true;
 | |
|                 internal.ongoingTransitionCount++;
 | |
|             }
 | |
| 
 | |
|             // Called when a transition has ended.
 | |
|             function transitionEnded()
 | |
|             {
 | |
|                 container.clip = false
 | |
|                 if (state != "")
 | |
|                     state = "Hidden";
 | |
| 
 | |
|                 internal.ongoingTransitionCount--;
 | |
|                 transitionAnimationRunning = false;
 | |
| 
 | |
|                 if (cleanupAfterTransition) {
 | |
|                     cleanup();
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             states: [
 | |
|                 // Explicit properties for default state.
 | |
|                 State {
 | |
|                     name: ""
 | |
|                     PropertyChanges { target: container; visible: true; opacity: 1 }
 | |
|                     PropertyChanges { target: container; width: container.implicitWidth}
 | |
|                 },
 | |
|                 // Start state for pop entry, end state for push exit.
 | |
|                 State {
 | |
|                     name: "Left"
 | |
|                     PropertyChanges { target: container; opacity: 0 }
 | |
|                     PropertyChanges { target: container; width: Units.gridUnit * 8}
 | |
|                 },
 | |
|                 // Start state for push entry, end state for pop exit.
 | |
|                 State {
 | |
|                     name: "Right"
 | |
|                     PropertyChanges { target: container; opacity: 0 }
 | |
|                     PropertyChanges { target: container; width: Units.gridUnit * 8}
 | |
|                 },
 | |
|                 // Inactive state.
 | |
|                 State {
 | |
|                     name: "Hidden"
 | |
|                     PropertyChanges { target: container; visible: false }
 | |
|                     PropertyChanges { target: container; width: container.implicitWidth}
 | |
|                 }
 | |
|             ]
 | |
| 
 | |
|             transitions: [
 | |
|                 // Push exit transition
 | |
|                 Transition {
 | |
|                     from: ""; to: "Left"
 | |
|                     SequentialAnimation {
 | |
|                         ScriptAction { script: transitionStarted() }
 | |
|                         ParallelAnimation {
 | |
|                             PropertyAnimation { properties: "width"; easing.type: Easing.InQuad; duration: internal.transitionDuration }
 | |
|                             PropertyAnimation { properties: "opacity"; easing.type: Easing.InQuad; duration: internal.transitionDuration }
 | |
|                         }
 | |
|                         ScriptAction { script: transitionEnded() }
 | |
|                     }
 | |
|                 },
 | |
|                 // Pop entry transition
 | |
|                 Transition {
 | |
|                     from: "Left"; to: ""
 | |
|                     SequentialAnimation {
 | |
|                         ScriptAction { script: transitionStarted() }
 | |
|                         ParallelAnimation {
 | |
|                             PropertyAnimation { properties: "width"; easing.type: Easing.OutQuad; duration: internal.transitionDuration }
 | |
|                             PropertyAnimation { properties: "opacity"; easing.type: Easing.InQuad; duration: internal.transitionDuration }
 | |
|                         }
 | |
|                         ScriptAction { script: transitionEnded() }
 | |
|                     }
 | |
|                 },
 | |
|                 // Pop exit transition
 | |
|                 Transition {
 | |
|                     from: ""; to: "Right"
 | |
|                     SequentialAnimation {
 | |
|                         ScriptAction { script: transitionStarted() }
 | |
|                         ParallelAnimation {
 | |
|                             PropertyAnimation { properties: "width"; easing.type: Easing.InQuad; duration: internal.transitionDuration }
 | |
|                             PropertyAnimation { properties: "opacity"; easing.type: Easing.InQuad; duration: internal.transitionDuration }
 | |
|                         }
 | |
|                         // Workaround for transition animation bug causing ghost view with page pop transition animation
 | |
|                         // TODO: Root cause still unknown
 | |
|                         PropertyAnimation {}
 | |
|                         ScriptAction { script: transitionEnded() }
 | |
|                     }
 | |
|                 },
 | |
|                 // Push entry transition
 | |
|                 Transition {
 | |
|                     from: "Right"; to: ""
 | |
|                     SequentialAnimation {
 | |
|                         ScriptAction { script: transitionStarted() }
 | |
|                         ParallelAnimation {
 | |
|                             PropertyAnimation { properties: "width"; easing.type: Easing.OutQuad; duration: internal.transitionDuration }
 | |
|                             PropertyAnimation { properties: "opacity"; easing.type: Easing.InQuad; duration: internal.transitionDuration }
 | |
|                         }
 | |
|                         ScriptAction { script: transitionEnded() }
 | |
|                     }
 | |
|                 }
 | |
|             ]
 | |
| 
 | |
|             // Cleans up the container and then destroys it.
 | |
|             function cleanup()
 | |
|             {
 | |
|                 if (page != null) {
 | |
|                     if (owner != container) {
 | |
|                         // container is not the owner of the page - re-parent back to original owner
 | |
|                         page.visible = false;
 | |
|                         page.anchors.fill = undefined
 | |
|                         page.parent = owner;
 | |
|                     }
 | |
|                 }
 | |
|                 container.parent = null;
 | |
|                 container.visible = false;
 | |
|                 destroy();
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 |