added android components
[mardrone] / mardrone / imports / com / nokia / android.1.1 / ScrollBar.qml
1 /****************************************************************************
2 **
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the Qt Components project.
8 **
9 ** $QT_BEGIN_LICENSE:BSD$
10 ** You may use this file under the terms of the BSD license as follows:
11 **
12 ** "Redistribution and use in source and binary forms, with or without
13 ** modification, are permitted provided that the following conditions are
14 ** met:
15 **   * Redistributions of source code must retain the above copyright
16 **     notice, this list of conditions and the following disclaimer.
17 **   * Redistributions in binary form must reproduce the above copyright
18 **     notice, this list of conditions and the following disclaimer in
19 **     the documentation and/or other materials provided with the
20 **     distribution.
21 **   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
22 **     the names of its contributors may be used to endorse or promote
23 **     products derived from this software without specific prior written
24 **     permission.
25 **
26 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27 ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28 ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
29 ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
30 ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31 ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32 ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
33 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
34 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
35 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
36 ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
37 ** $QT_END_LICENSE$
38 **
39 ****************************************************************************/
40
41 import QtQuick 1.1
42 import "SectionScroller.js" as Sections
43 import "." 1.1
44
45 Item {
46     id: root
47
48     property Flickable flickableItem: null
49     property int orientation: Qt.Vertical
50     property bool interactive: true
51     property int policy: Android.ScrollBarWhenScrolling
52     property bool privateSectionScroller: false
53     property bool platformInverted: false
54
55     //implicit values for qml designer when no Flickable is present
56     implicitHeight: privateStyle.scrollBarThickness * (orientation == Qt.Vertical ? 3 : 1)
57     implicitWidth: privateStyle.scrollBarThickness * (orientation == Qt.Horizontal ? 3 : 1)
58     height: {
59         if (flickableItem && orientation == Qt.Vertical)
60             return Math.floor(Math.round(flickableItem.height) - anchors.topMargin - anchors.bottomMargin)
61         return undefined
62     }
63     width: {
64         if (flickableItem && orientation == Qt.Horizontal)
65             return Math.floor(Math.round(flickableItem.width) - anchors.leftMargin - anchors.rightMargin)
66         return undefined
67     }
68     opacity: internal.rootOpacity
69
70     onFlickableItemChanged: internal.initSectionScroller();
71     onPrivateSectionScrollerChanged: internal.initSectionScroller();
72
73     //For showing explicitly a ScrollBar if policy is Android.ScrollBarWhenScrolling
74     function flash(type) {
75         if (policy == Android.ScrollBarWhenScrolling && internal.scrollBarNeeded) {
76             flashEffect.type = (type == undefined) ? Android.FadeOut : type
77             flashEffect.restart()
78         }
79     }
80
81     Connections {
82         target: screen
83         onCurrentOrientationChanged: flash()
84     }
85
86     Connections {
87         target: root.privateSectionScroller ? flickableItem : null
88         onModelChanged: internal.initSectionScroller()
89     }
90
91     Loader {
92         id: listItemInteraction
93         sourceComponent: flickableItem && flickableItem.hasOwnProperty("currentIndex") && flickableItem.activeFocus ? listItemInteractionComponent : undefined
94     }
95
96     Component {
97         id: listItemInteractionComponent
98         Item {
99             Connections {
100                 target: symbian
101                 onListInteractionModeChanged: flash()
102                 onPrivateListItemKeyNavigation: flash()
103             }
104         }
105     }
106
107     QtObject {
108         id: internal
109         property int hideTimeout: 500
110         property int pageStepY: flickableItem ? Math.floor(flickableItem.visibleArea.heightRatio * flickableItem.contentHeight) : NaN
111         property int pageStepX: flickableItem ? Math.floor(flickableItem.visibleArea.widthRatio * flickableItem.contentWidth) : NaN
112         property int handleY: flickableItem ? Math.floor(handle.y / flickableItem.height * flickableItem.contentHeight) : NaN
113         property int handleX: flickableItem ? Math.floor(handle.x / flickableItem.width * flickableItem.contentWidth) : NaN
114         property int maximumY: flickableItem ? Math.floor(Math.min(flickableItem.contentHeight - root.height, flickableItem.contentY)) : NaN
115         property int maximumX: flickableItem ? Math.floor(Math.min(flickableItem.contentWidth - root.width, flickableItem.contentX)) : NaN
116         property bool scrollBarNeeded: root.visible && hasScrollableContent()
117         //Sets currentSection to empty string when flickableItem.currentSection is null
118         property string currentSection: flickableItem ? flickableItem.currentSection || "" : ""
119         //To be able to pressed on trackMouseArea, opacity needs to be greater than 0
120         property real rootOpacity: root.privateSectionScroller ? 0.01 : 0
121
122         // The following properties are used for calculating handle position and size:
123         // Minimum allowed handle length
124         property real minHandleLength: 3 * Math.floor(privateStyle.scrollBarThickness)
125         // Scroll bar "container" length
126         property real totalLength: !flickableItem ? NaN : (orientation == Qt.Vertical ? root.height : root.width)
127         property real relativeHandlePosition: !flickableItem ? NaN : (orientation == Qt.Vertical
128                                                                       ? flickableItem.visibleArea.yPosition
129                                                                       : flickableItem.visibleArea.xPosition)
130         property real lengthRatio: !flickableItem ? NaN : (orientation == Qt.Vertical
131                                                            ? flickableItem.visibleArea.heightRatio
132                                                            : flickableItem.visibleArea.widthRatio)
133         // Scroll bar handle length under normal circumstances
134         property real staticHandleLength: Math.max(minHandleLength, lengthRatio * totalLength)
135         // Adjustment factor needed to calculate correct position, which is needed because
136         // relativeHandlePosition and lengthRatio are assuming default handle length. Dividend is
137         // the maximum handle position taking into account restricted minimum handle length, whereas
138         // divisor is the maximum handle position if default handle size would be used.
139         property real handlePositionAdjustment: (totalLength - staticHandleLength) / (totalLength * (1 - lengthRatio))
140         property real handlePosition
141         handlePosition: {
142             // handle's position, which is adjusted to take into account non-default handle
143             // length and restricted never to exceed flickable boundaries
144             var pos = totalLength * relativeHandlePosition * handlePositionAdjustment
145             return Math.min(Math.max(0, pos), totalLength - minHandleLength)
146         }
147         property real dynamicHandleLength
148         dynamicHandleLength: {
149             // dynamic handle length, which may differ from static due to bounds behavior, i.e.
150             // handle gets shorter when content exceeds flickable boundaries
151             var length = 0
152             if (relativeHandlePosition >= 1 - lengthRatio) // overflow
153                 length = 1 - relativeHandlePosition
154             else if (relativeHandlePosition < 0) // underflow
155                 length = lengthRatio + relativeHandlePosition
156             else
157                 length = lengthRatio
158             return Math.max(minHandleLength, length * totalLength)
159         }
160
161         // Show flash effect in case if no flicking (flickableItem.moving) has occured
162         // but there is a need to indicate scrollable content.
163         onScrollBarNeededChanged: {
164             if (stateGroup.state == "") {
165                 if (scrollBarNeeded)
166                     flash()
167                 else
168                     idleEffect.restart()
169             }
170         }
171
172         /**
173          * Checks whether ScrollBar is needed or not
174          * based on Flickable visibleArea height and width ratios
175          */
176         function hasScrollableContent() {
177             if (!flickableItem)
178                 return false
179             var ratio = orientation == Qt.Vertical ? flickableItem.visibleArea.heightRatio : flickableItem.visibleArea.widthRatio
180             return ratio < 1.0 && ratio > 0
181         }
182         /**
183          * Does Page by Page movement of flickableItem
184          * when ScrollBar Track is being clicked/pressed
185          *
186          * @see #moveToLongTapPosition
187          */
188         function doPageStep() {
189             if (orientation == Qt.Vertical) {
190                 if (trackMouseArea.mouseY > (handle.height / 2 + handle.y)) {
191                     flickableItem.contentY += pageStepY
192                     flickableItem.contentY = maximumY
193                 }
194                 else if (trackMouseArea.mouseY < (handle.height / 2 + handle.y)) {
195                     flickableItem.contentY -= pageStepY
196                     flickableItem.contentY = Math.max(0, flickableItem.contentY)
197                 }
198             } else {
199                 if (trackMouseArea.mouseX > (handle.width / 2 + handle.x)) {
200                     flickableItem.contentX += pageStepX
201                     flickableItem.contentX = maximumX
202                 }
203                 else if (trackMouseArea.mouseX < (handle.width / 2 + handle.x)) {
204                     flickableItem.contentX -= pageStepX
205                     flickableItem.contentX = Math.max(0, flickableItem.contentX)
206                 }
207             }
208         }
209         /**
210          * Does movement of flickableItem
211          * when ScrollBar Handle is being dragged
212          */
213         function moveToHandlePosition() {
214             if (orientation == Qt.Vertical)
215                 flickableItem.contentY = handleY
216             else
217                 flickableItem.contentX = handleX
218         }
219         /**
220          * Moves flickableItem's content according to given mouseArea movement
221          * when mouseArea is pressed long
222          * Tries to position the handle and content in center of mouse position enough
223          *
224          * @see #doPageStep
225          */
226         function moveToLongTapPosition(mouseArea) {
227             if (orientation == Qt.Vertical) {
228                 if (Math.abs(mouseArea.mouseY - (handle.height / 2 + handle.y)) < privateStyle.scrollBarThickness)
229                     return //if change is not remarkable enough, do nothing otherwise it would cause annoying flickering effect
230                 if (mouseArea.mouseY > (handle.height / 2 + handle.y)) {
231                     flickableItem.contentY += Math.floor(privateStyle.scrollBarThickness)
232                     flickableItem.contentY = maximumY
233                 }
234                 else if (mouseArea.mouseY < (handle.height / 2 + handle.y)) {
235                     flickableItem.contentY -= Math.floor(privateStyle.scrollBarThickness)
236                     flickableItem.contentY = Math.floor(Math.max(0, flickableItem.contentY))
237                 }
238             } else {
239                 if (Math.abs(mouseArea.mouseX - (handle.width / 2 + handle.x)) < privateStyle.scrollBarThickness)
240                     return //if change is not remarkable enough, do nothing otherwise it would cause annoying flickering effect
241                 if (mouseArea.mouseX > (handle.width / 2 + handle.x)) {
242                     flickableItem.contentX += Math.floor(privateStyle.scrollBarThickness)
243                     flickableItem.contentX = maximumX
244                 }
245                 else if (mouseArea.mouseX < (handle.width / 2 + handle.x)) {
246                     flickableItem.contentX -= Math.floor(privateStyle.scrollBarThickness)
247                     flickableItem.contentX = Math.floor(Math.max(0, flickableItem.contentX))
248                 }
249             }
250         }
251
252         function adjustContentPosition(y) {
253             if (y < 0 || y > trackMouseArea.height)
254                 return;
255
256             var sect = Sections.closestSection(y / trackMouseArea.height);
257             var idx = Sections.indexOf(sect);
258             currentSection = sect;
259             flickableItem.positionViewAtIndex(idx, ListView.Beginning);
260         }
261
262         function initSectionScroller() {
263             if (root.privateSectionScroller && flickableItem && flickableItem.model)
264                 Sections.initSectionData(flickableItem);
265         }
266     }
267
268     BorderImage {
269         id: track
270         objectName: "track"
271         source: privateStyle.imagePath((orientation == Qt.Vertical
272                                         ? "qtg_fr_scrollbar_v_track_normal"
273                                         : "qtg_fr_scrollbar_h_track_normal"),
274                                        root.platformInverted)
275         visible: interactive
276         anchors.fill: parent
277         border.right: orientation == Qt.Horizontal ? 7 : 0
278         border.left: orientation == Qt.Horizontal ? 7 : 0
279         border.top: orientation == Qt.Vertical ? 7 : 0
280         border.bottom: orientation == Qt.Vertical ? 7 : 0
281         onVisibleChanged: { idleEffect.complete(); flashEffect.complete() }
282     }
283
284     Loader {
285         id: sectionScrollBackground
286         anchors.right: trackMouseArea.right
287         width: flickableItem ? flickableItem.width : 0
288         height: platformStyle.fontSizeMedium * 5
289         sourceComponent: root.privateSectionScroller ? sectionScrollComponent : null
290     }
291
292     Component {
293         id: sectionScrollComponent
294         BorderImage {
295             id: indexFeedbackBackground
296             objectName: "indexFeedbackBackground"
297             source: privateStyle.imagePath("qtg_fr_popup_transparent", root.platformInverted)
298             border { left: platformStyle.borderSizeMedium; top: platformStyle.borderSizeMedium; right: platformStyle.borderSizeMedium; bottom: platformStyle.borderSizeMedium }
299             visible: trackMouseArea.pressed
300             anchors.fill: parent
301             Text {
302                 id: indexFeedbackText
303                 objectName: "indexFeedbackText"
304                 color: root.platformInverted ? platformStyle.colorNormalDarkInverted // intentionally dark inverted
305                                              : platformStyle.colorNormalLight
306                 anchors {
307                     left: parent.left;
308                     leftMargin: platformStyle.paddingLarge;
309                     right: parent.right;
310                     rightMargin: platformStyle.paddingLarge;
311                     verticalCenter: parent.verticalCenter
312                 }
313                 font {
314                     family: platformStyle.fontFamilyRegular;
315                     pixelSize: indexFeedbackText.text.length == 1 ? platformStyle.fontSizeMedium * 4 : platformStyle.fontSizeMedium * 2;
316                     capitalization: indexFeedbackText.text.length == 1 ? Font.AllUppercase : Font.MixedCase
317                 }
318                 text: internal.currentSection
319                 horizontalAlignment: Text.AlignLeft
320                 elide: Text.ElideRight
321             }
322             states: [
323                 State {
324                     when: (handle.y + (handle.height / 2)) - (sectionScrollBackground.height / 2) < 0
325                     AnchorChanges {
326                         target: sectionScrollBackground
327                         anchors { verticalCenter: undefined; top: track.top; bottom: undefined }
328                     }
329                 },
330                 State {
331                     when: (handle.y + (handle.height / 2)) + (sectionScrollBackground.height / 2) >= track.height
332                     AnchorChanges {
333                         target: sectionScrollBackground
334                         anchors { verticalCenter: undefined; top: undefined; bottom: track.bottom }
335                     }
336                 },
337                 State {
338                     when: (handle.y + (handle.height / 2)) - (sectionScrollBackground.height / 2) >= 0
339                     AnchorChanges {
340                         target: sectionScrollBackground
341                         anchors { verticalCenter: handle.verticalCenter; top: undefined; bottom: undefined }
342                     }
343                 }
344             ]
345         }
346     }
347
348     // MouseArea for the move content "page by page" by tapping and scroll to press-and-hold position
349     MouseArea {
350         id: trackMouseArea
351         objectName: "trackMouseArea"
352         property bool longPressed: false
353         enabled: root.privateSectionScroller || interactive
354         anchors {
355             top: root.privateSectionScroller ? parent.top : undefined;
356             bottom: root.privateSectionScroller ? parent.bottom : undefined;
357             right: root.privateSectionScroller ? parent.right : undefined;
358             fill: root.privateSectionScroller ? undefined : (flickableItem ? track : undefined)
359         }
360         width: root.privateSectionScroller ? privateStyle.scrollBarThickness * 3 : undefined
361         drag {
362             target: root.privateSectionScroller ? sectionScrollBackground : undefined
363             // axis is set XandY to prevent flickable from stealing the mouse event
364             // SectionScroller is anchored to the right side of the mouse area so the user
365             // won't be able to drag it along the X axis
366             axis: root.privateSectionScroller ? Drag.XandYAxis : 0
367             minimumY: root.privateSectionScroller ? (flickableItem ? flickableItem.y : 0) : 0
368             maximumY: root.privateSectionScroller ? (flickableItem ? (trackMouseArea.height - sectionScrollBackground.height) : 0) : 0
369         }
370
371         onPressAndHold: {
372             if (!root.privateSectionScroller)
373                 longPressed = true
374         }
375
376         onReleased: longPressed = false
377
378         onPositionChanged: {
379             if (root.privateSectionScroller)
380                 internal.adjustContentPosition(trackMouseArea.mouseY);
381         }
382
383         onPressedChanged: {
384             if (root.privateSectionScroller && trackMouseArea.pressed)
385                 internal.adjustContentPosition(trackMouseArea.mouseY);
386         }
387     }
388     Timer {
389         id: pressAndHoldTimer
390         running: trackMouseArea.longPressed
391         interval: 50
392         repeat: true
393         onTriggered: { internal.moveToLongTapPosition(trackMouseArea); privateStyle.play(Android.SensitiveSlider) }
394     }
395
396     BorderImage {
397         id: handle
398         objectName: "handle"
399         source: privateStyle.imagePath(handleFileName(), root.platformInverted)
400         x: orientation == Qt.Horizontal ? internal.handlePosition : NaN
401         y: orientation == Qt.Vertical ? internal.handlePosition : NaN
402         height: orientation == Qt.Vertical ? internal.dynamicHandleLength : root.height
403         width: orientation == Qt.Horizontal ? internal.dynamicHandleLength : root.width
404         border.right: orientation == Qt.Horizontal ? 7 : 0
405         border.left: orientation == Qt.Horizontal ? 7 : 0
406         border.top: orientation == Qt.Vertical ? 7 : 0
407         border.bottom: orientation == Qt.Vertical ? 7 : 0
408
409         function handleFileName() {
410             var fileName = (orientation == Qt.Vertical ? "qtg_fr_scrollbar_v_handle_" :
411                             "qtg_fr_scrollbar_h_handle_")
412             if (!interactive)
413                 fileName += "indicative"
414             else if (handleMouseArea.pressed)
415                 fileName += "pressed"
416             else
417                 fileName += "normal"
418             return fileName
419         }
420     }
421
422     MouseArea {
423         id: handleMouseArea
424         objectName: "handleMouseArea"
425         property real maxDragY: flickableItem ? flickableItem.height - handle.height - root.anchors.topMargin - root.anchors.bottomMargin : NaN
426         property real maxDragX: flickableItem ? flickableItem.width - handle.width - root.anchors.leftMargin - root.anchors.rightMargin : NaN
427         enabled: interactive && !root.privateSectionScroller
428         width: orientation == Qt.Vertical ? 3 * privateStyle.scrollBarThickness : handle.width
429         height: orientation == Qt.Horizontal ? 3 * privateStyle.scrollBarThickness : handle.height
430         anchors {
431             verticalCenter: flickableItem ? handle.verticalCenter : undefined
432             horizontalCenter: flickableItem ? handle.horizontalCenter : undefined
433         }
434         drag {
435             target: handle
436             axis: orientation == Qt.Vertical ? Drag.YAxis : Drag.XAxis
437             minimumY: 0
438             maximumY: maxDragY
439             minimumX: 0
440             maximumX: maxDragX
441         }
442         onPositionChanged: internal.moveToHandlePosition()
443     }
444
445     PropertyAnimation {
446         id: indicateEffect
447
448         function play() {
449             flashEffect.stop()
450             idleEffect.stop()
451             restart()
452         }
453
454         target: root
455         property: "opacity"
456         to: 1
457         duration: 0
458     }
459     SequentialAnimation {
460         id: idleEffect
461
462         function play() {
463             indicateEffect.stop()
464             if (internal.scrollBarNeeded && root.policy == Android.ScrollBarWhenScrolling)
465                 restart()
466         }
467
468         PauseAnimation { duration: root.interactive ? 1500 : 0 }
469         PropertyAnimation {
470             target: root
471             property: "opacity"
472             to: internal.rootOpacity
473             duration: internal.hideTimeout
474         }
475     }
476     SequentialAnimation {
477         id: flashEffect
478         property int type: Android.FadeOut
479
480         PropertyAnimation {
481             target: root
482             property: "opacity"
483             to: 1
484             duration: (flashEffect.type == Android.FadeInFadeOut) ? internal.hideTimeout : 0
485         }
486         PropertyAnimation {
487             target: root
488             property: "opacity"
489             to: internal.rootOpacity
490             duration: internal.hideTimeout
491         }
492     }
493     StateGroup {
494         id: stateGroup
495         states: [
496             State {
497                 name: "Move"
498                 when: handleMouseArea.pressed
499             },
500             State {
501                 name: "Stepping"
502                 when: trackMouseArea.longPressed
503             },
504             State {
505                 name: "Step"
506                 when: trackMouseArea.pressed && !trackMouseArea.longPressed && !root.privateSectionScroller
507             },
508             State {
509                 name: "Indicate"
510                 when: (internal.scrollBarNeeded && flickableItem.moving) ||
511                       (trackMouseArea.pressed && root.privateSectionScroller)
512             },
513             State {
514                 name: ""
515             }
516         ]
517         transitions: [
518             Transition {
519                 to: "Move"
520                 ScriptAction { script: privateStyle.play(Android.BasicSlider) }
521                 ScriptAction { script: indicateEffect.play() }
522             },
523             Transition {
524                 to: "Step"
525                 ScriptAction { script: internal.doPageStep() }
526                 ScriptAction { script: privateStyle.play(Android.BasicSlider) }
527                 ScriptAction { script: indicateEffect.play() }
528             },
529             Transition {
530                 from: "Step"
531                 to: "Stepping"
532                 ScriptAction { script: indicateEffect.play() }
533             },
534             Transition {
535                 from: "Move"
536                 to: "Indicate"
537                 ScriptAction { script: privateStyle.play(Android.BasicSlider) }
538                 ScriptAction { script: indicateEffect.play() }
539             },
540             Transition {
541                 to: "Indicate"
542                 ScriptAction { script: indicateEffect.play() }
543             },
544             Transition {
545                 from: "Move"
546                 to: ""
547                 ScriptAction { script: privateStyle.play(Android.BasicSlider) }
548                 ScriptAction { script: idleEffect.play() }
549             },
550             Transition {
551                 to: ""
552                 ScriptAction { script: idleEffect.play() }
553             }
554         ]
555     }
556 }