~ubuntu-sdk-team/ubuntu-ui-toolkit/trunk

« back to all changes in this revision

Viewing changes to src/Ubuntu/Components/Themes/Ambiance/1.3/ScrollbarStyle.qml

  • Committer: CI Train Bot
  • Author(s): Christian Dywan, Zsombor Egri, Zoltán Balogh, Tim Peeters, Albert Astals Cid, Michael Sheldon, Benjamin Zeller
  • Date: 2015-12-17 17:13:49 UTC
  • mfrom: (1000.739.27 OTA9-landing-2015-12-16)
  • Revision ID: ci-train-bot@canonical.com-20151217171349-8xwclnhnx8v9oz4m
OTA9-landing-2015-12-16
Approved by: Zoltan Balogh

Show diffs side-by-side

added added

removed removed

Lines of Context:
19
19
 
20
20
/*
21
21
  The visuals handle both active and passive modes. This behavior is driven yet by
22
 
  the styledItem's __inactive property, however should be detected upon runtime based on
 
22
  the styledItem's __interactive property, however should be detected upon runtime based on
23
23
  the device type.
24
24
  On active scrollbars, positioning is handled so that the logic updates the flickable's
25
25
  X/Y content positions, which is then synched with the contentPosition by the main
32
32
        - overlay: bool - true if the scrollbar is overlay type
33
33
        - overlayOpacityWhenHidden: opacity when hidden
34
34
        - overlayOpacityWhenShown: opacity when shown
 
35
    * trough
 
36
        - troughThicknessIndicatorStyle: real - thickness of the thumb in Indicator style
 
37
        - troughThicknessThumbStyle: real - thickness of the thumb in Thumb style
 
38
        - troughThicknessSteppersStyle: real - thickness of the thumb in Steppers style
 
39
        - troughColorThumbStyle: color - color of the trough in Thumb style
 
40
        - troughColorSteppersStyle: color - color of the trough in Steppers style
 
41
    * slider
 
42
        - sliderColor: color - color for the slider
 
43
        - sliderRadius: real - radius of of the slider rectangle
 
44
        - thumbThickness: real - thickness of the slider in Thumb style
 
45
        - indicatorThickness: real - thickness of the slider in Indicator style
35
46
    * animations - where duration and easing properties are used only
36
 
        - scrollbarFadeInAnimation: PropertyAnimation - animation used when fade in
37
 
        - scrollbarFadeOutAnimation: PropertyAnimation - animation used when fade out
38
 
        - scrollbarFadeOutPause: int - miliseconds to pause before fade out
39
 
    * behaviors - animations are used as declared
40
 
        - sliderAnimation: PropertyAnimation - animation for the slider size
41
 
        - thumbConnectorFading: PropertyAnimation - animation for the thumb connector
42
 
        - thumbFading: PropertyAnimation - animation for the thumb fading
 
47
        - scrollbarThicknessAnimation: PropertyAnimation - animation used when changing
 
48
                                       the thickness of the slider
 
49
        - scrollbarFadeInAnimation: PropertyAnimation - animation used when fading in
 
50
        - scrollbarFadeOutAnimation: PropertyAnimation - animation used when fading out
 
51
        - scrollbarFadeOutPause: int - milliseconds to pause before fading out
 
52
        - scrollbarCollapsePause: int - milliseconds to pause at the beginning of the
 
53
                                        transition from Thumb to Indicator style
 
54
    * scrolling UX - the values are relative to the size of the view
 
55
        - shortScrollingRatio: real - how much to scroll when using arrow keys and steppers
 
56
        - longScrollingRatio: real - how much to scroll when using PageUp/Down
43
57
    * other styling properties
44
 
        - color sliderColor: color for the slider
45
 
        - color thumbConnectorColor: thumb connector color
46
 
        - url forwardThumbReleased: forward thumb image when released
47
 
        - url forwardThumbPressed: forward thumb image when pressed
48
 
        - url backwardThumbReleased: backward thumb image when released
49
 
        - url backwardThumbPressed: backward thumb image when pressed
50
 
        - real scrollAreaThickness: scrollbar area thickness, the area where the
51
 
                                    slider, thumb and thumb-connector appear
52
 
        - real thumbConnectorMargin: margin of the thumb connector aligned to the
53
 
                                    thumb visuals
 
58
        - hintingStyle: string - the style to use for the hinting feature
 
59
        - thumbsExtremesMargin: real - This is the top/bottom (for the vertical scrollbar)
 
60
                                       and left/right (for horiz scrollbar) margin of the slider
 
61
                                       from the trough.
 
62
                                       It is supposed to be the same margin as the one that
 
63
                                       separates the thumb from the left/right (or top/bottom
 
64
                                       for horizontal scrollbar) edges of the screen.
54
65
  */
55
66
 
56
67
Item {
57
68
    id: visuals
58
 
    // styling properties
59
 
    property bool interactive: false
60
 
    property real minimumSliderSize: units.gu(2)
61
 
 
62
 
    property bool overlay: !interactive
63
 
    property real overlayOpacityWhenShown: 0.6
 
69
 
 
70
    property bool initialized: false
 
71
 
 
72
    /*****************************************************
 
73
     *      STYLING PROPERTIES                           *
 
74
     *****************************************************/
 
75
    property bool interactive: isMouseConnected || veryLongContentItem
 
76
    property real minimumSliderSize: units.gu(3)
 
77
 
 
78
    property bool overlay: !alwaysOnScrollbars
 
79
    property real overlayOpacityWhenShown: 1.0
64
80
    property real overlayOpacityWhenHidden: 0.0
65
81
 
66
 
    property PropertyAnimation scrollbarFadeInAnimation: UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration }
 
82
    property real troughThicknessSteppersStyle : units.dp(14)
 
83
    property real troughThicknessThumbStyle : units.dp(14)
 
84
    property real troughThicknessIndicatorStyle : units.dp(9)
 
85
    property color troughColorThumbStyle: "#CDCDCD"
 
86
    property color troughColorSteppersStyle: "#f7f7f7"
 
87
 
 
88
    property color sliderColor: "#3b3b3b"
 
89
    property real sliderRadius: units.dp(3)
 
90
    property real thumbThickness: units.gu(1)
 
91
    property real indicatorThickness : units.dp(3)
 
92
 
 
93
    property PropertyAnimation scrollbarThicknessAnimation: UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration }
 
94
    property PropertyAnimation scrollbarFadeInAnimation: UbuntuNumberAnimation { duration: UbuntuAnimation.SlowDuration }
67
95
    property PropertyAnimation scrollbarFadeOutAnimation: UbuntuNumberAnimation { duration: UbuntuAnimation.SlowDuration }
68
 
    property int scrollbarFadeOutPause: 300
69
 
    property PropertyAnimation sliderAnimation: UbuntuNumberAnimation {}
70
 
    property PropertyAnimation thumbConnectorFading: UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration }
71
 
    property PropertyAnimation thumbFading: UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration }
72
 
 
73
 
    property color sliderColor: theme.palette.normal.base
74
 
    property real sliderRadius: units.gu(0.5)
75
 
    property color thumbConnectorColor: "white"
76
 
    property url forwardThumbReleased: (styledItem.align === Qt.AlignLeading || styledItem.align === Qt.AlignTrailing) ? Qt.resolvedUrl("../artwork/ScrollbarBottomIdle.png") : Qt.resolvedUrl("../artwork/ScrollbarRightIdle.png")
77
 
    property url forwardThumbPressed: (styledItem.align === Qt.AlignLeading || styledItem.align === Qt.AlignTrailing) ? Qt.resolvedUrl("../artwork/ScrollbarBottomPressed.png") : Qt.resolvedUrl("../artwork/ScrollbarRightPressed.png")
78
 
    property url backwardThumbReleased: (styledItem.align === Qt.AlignLeading || styledItem.align === Qt.AlignTrailing) ? Qt.resolvedUrl("../artwork/ScrollbarTopIdle.png") : Qt.resolvedUrl("../artwork/ScrollbarLeftIdle.png")
79
 
    property url backwardThumbPressed: (styledItem.align === Qt.AlignLeading || styledItem.align === Qt.AlignTrailing) ? Qt.resolvedUrl("../artwork/ScrollbarTopPressed.png") : Qt.resolvedUrl("../artwork/ScrollbarLeftPressed.png")
80
 
 
81
 
    property real scrollAreaThickness: units.gu(0.5)
82
 
    property real thumbConnectorMargin: units.dp(3)
83
 
 
84
 
    // helper properties to ease code readability
85
 
    property Flickable flickableItem: styledItem.flickableItem
 
96
    property int scrollbarFadeOutPause: 3000
 
97
    property int scrollbarCollapsePause: 1000
 
98
 
 
99
    //The ratio of page to scroll when using pg_down/up
 
100
    //and arrow_down/up keyboard keys (and steppers)
 
101
    property real shortScrollingRatio: 0.1
 
102
    property real longScrollingRatio: 0.9
 
103
 
 
104
    property string hintingStyle: veryLongContentItem ? 'thumb' : 'indicator'
 
105
    onHintingStyleChanged: flashScrollbar()
 
106
 
 
107
    property real thumbsExtremesMargin: units.dp(4)
 
108
 
 
109
    /*****************************************************
 
110
     *      HELPER PROPERTIES                            *
 
111
     *****************************************************/
 
112
    property alias thumb: slider
 
113
    property Item trough: trough
 
114
 
 
115
    //helper properties to ease code readability
86
116
    property bool isScrollable: styledItem.__private.scrollable && pageSize > 0.0
87
 
                                && contentSize > 0.0 && contentSize > pageSize
 
117
                                && contentSize > 0.0 && totalContentSize > pageSize
88
118
    property bool isVertical: (styledItem.align === Qt.AlignLeading) || (styledItem.align === Qt.AlignTrailing)
89
119
    property bool frontAligned: (styledItem.align === Qt.AlignLeading)
90
120
    property bool rearAligned: (styledItem.align === Qt.AlignTrailing)
91
121
    property bool topAligned: (styledItem.align === Qt.AlignTop)
92
122
    property bool bottomAligned: (styledItem.align === Qt.AlignBottom)
93
123
 
94
 
    property real pageSize: (isVertical) ? styledItem.height : styledItem.width
 
124
    //flickable helper properties
 
125
    property Flickable flickableItem: styledItem.flickableItem
 
126
    property real pageSize: (isVertical) ? (styledItem.flickableItem.height) : (styledItem.flickableItem.width)
95
127
    property real contentSize: (isVertical) ? styledItem.flickableItem.contentHeight : styledItem.flickableItem.contentWidth
 
128
    property real leadingContentMargin: isVertical ? styledItem.flickableItem.topMargin : styledItem.flickableItem.leftMargin
 
129
    property real trailingContentMargin: isVertical ? styledItem.flickableItem.bottomMargin : styledItem.flickableItem.rightMargin
 
130
    //this size includes content margins
 
131
    property real totalContentSize: contentSize + leadingContentMargin + trailingContentMargin
 
132
 
 
133
 
 
134
    /*****************************************************
 
135
     *      INTERNAL PROPERTIES AND FUNCTIONS            *
 
136
     *****************************************************/
 
137
    property bool __recursionGuard: false
 
138
    property bool __disableStateBinding: false
 
139
    property alias __overshootTimer: overshootTimer
 
140
    property bool __hinting: false
 
141
    property bool draggingThumb: thumbArea.drag.active || slider.mouseDragging || slider.touchDragging
 
142
    //this is the condition that triggers "Thumb Style"
 
143
    //only show the thumb if the page AND the view is moving
 
144
    property bool thumbStyleFlag: veryLongContentItem && (flickableItem.moving || scrollAnimation.running)
 
145
    //we show thumb style instead of indicator style if the content item is very long on *any* of the 2 axes
 
146
    property bool veryLongContentItem: flickableItem && initialized
 
147
                                       && ((flickableItem.contentHeight > flickableItem.height * 10)
 
148
                                           || (flickableItem.contentWidth > flickableItem.width * 10))
 
149
 
 
150
    //this will eventually come from QInputInfo
 
151
    property bool isMouseConnected: true
 
152
    //used to offset viewport and scrollbars to make space for non-overlay scrollbars
 
153
    property real nonOverlayScrollbarMargin: troughThicknessSteppersStyle
 
154
    //TODO: move to Scrollbar.qml?
 
155
    property bool useSteppers: true
 
156
    //the thumb will have a touchDragStartMargin margin on both sides
 
157
    //to be more easily draggable via touch
 
158
    property real touchDragStartMargin: units.gu(2)
 
159
    //this could eventually become a system setting
 
160
    //True --> Steppers style, non-overlay scrollbars
 
161
    //False --> Indicator, Trough and Steppers styles, overlaid to the content
 
162
    property bool alwaysOnScrollbars: styledItem.__alwaysOnScrollbars
 
163
    function scroll(amount) {
 
164
        slider.scroll(amount)
 
165
    }
 
166
    function scrollToBeginning() {
 
167
        scrollAnimation.to = flickableItem[scrollbarUtils.propOrigin] - visuals.leadingContentMargin
 
168
        scrollAnimation.restart()
 
169
    }
 
170
    function scrollToEnd() {
 
171
        scrollAnimation.to = flickableItem[scrollbarUtils.propOrigin]
 
172
                + totalContentSize - visuals.leadingContentMargin - pageSize
 
173
        scrollAnimation.restart()
 
174
    }
 
175
    function resetScrollingToPreDrag() {
 
176
        thumbArea.resetFlickableToPreDragState()
 
177
    }
 
178
    //As per spec, we show a hint of the scrollbars in indicator style whenever the
 
179
    //flickable item changes its size
 
180
    //NOTE: THIS IS ASSUMING THAT the transition to hidden state has a PauseAnimation,
 
181
    //otherwise the hinting would be too quick!
 
182
    function flashScrollbar() {
 
183
        //only show the hint if the scrollbar is currently hidden or its already showing the hint
 
184
        //(it could happen that while it is showing the hint the size of the flickable changes enough
 
185
        //to trigger the transition to thumb style, and in that case we want to hint again using thumb
 
186
        //style)
 
187
        if (initialized && isScrollable && !draggingThumb && !pressHoldTimer.running
 
188
                && (state == '' || state === 'hidden' || (__disableStateBinding && visuals.state !== hintingStyle))) {
 
189
            __disableStateBinding = true
 
190
            __hinting = true
 
191
            visuals.state = hintingStyle
 
192
            hintingTimer.restart()
 
193
        }
 
194
    }
 
195
 
 
196
    anchors.fill: parent
 
197
    opacity: overlayOpacityWhenHidden
 
198
    Component.onCompleted: initialized = true
 
199
 
 
200
    //we can't use onRunningChanged in the state transitions to enable the state binding again
 
201
    //after hinting, because that was causing
 
202
    //"Can't apply a state change as part of a state definition." when the state it was transitioning
 
203
    //to was different from the one the binding evaluationg was returning
 
204
    //Instead we use a timer, tune it so that it lasts as long as needed, and then we reenable the
 
205
    //state binding once the transition has completed
 
206
    Timer {
 
207
        id: hintingTimer
 
208
        repeat: false
 
209
        //we assume thickness animation duration is enough to complete the
 
210
        //transition to the hinting style
 
211
        interval: scrollbarThicknessAnimation.duration
 
212
        onTriggered: {
 
213
            __hinting = false
 
214
            __disableStateBinding = false
 
215
        }
 
216
    }
96
217
 
97
218
    /*!
98
 
      \internal
99
 
      Object storing property names used in calculations.
 
219
        \internal
 
220
        Object storing property names used in calculations.
100
221
    */
101
222
    QtObject {
102
223
        id: scrollbarUtils
103
 
 
104
224
        property string propOrigin: (isVertical) ? "originY" : "originX"
105
225
        property string propContent: (isVertical) ? "contentY" : "contentX"
106
226
        property string propPosRatio: (isVertical) ? "yPosition" : "xPosition"
109
229
        property string propSize: (isVertical) ? "height" : "width"
110
230
 
111
231
        /*!
112
 
          \internal
113
232
          Calculates the slider position based on the visible area's ratios.
114
233
          */
115
 
        function sliderPos(min, max) {
116
 
            return MathUtils.clamp(styledItem.flickableItem.visibleArea[propPosRatio] * styledItem.flickableItem[propSize], min, max);
 
234
        function sliderPos(scrollbar, min, max) {
 
235
            //the margin between the trough and the thumb min and max values
 
236
            var margin = scrollbar.__styleInstance.thumbsExtremesMargin
 
237
 
 
238
            //The total length of the path where the thumb can be positioned, from its min to its max value
 
239
            var draggableLength = scrollbar.__trough[propSize] - margin*2
 
240
            var maxPosRatio = 1.0 - scrollbar.flickableItem.visibleArea[propSizeRatio]
 
241
 
 
242
            //Example with x/width, same applies to y/height
 
243
            //xPosition is in the range [0...1 - widthRatio]
 
244
            //and we want the following mapping (xPosition --> thumb.x):
 
245
            //   0              ---> margin (margin is the min position for the thumb)
 
246
            //   1 - widthRatio ---> (draggableLength - scrollbar.__styleInstance.thumb[propSize]) + margin
 
247
            //So we scale the maximum thumb position by xPosition. But that's not enough, because that would mean
 
248
            //the maxPosition is reached when xPosition becomes 1, and that never happens. To compensate that, we
 
249
            //scale xPosition by ( 1 / ( 1 - widthRatio) ). This way, when xPosition reaches its max ( 1 - widthRatio )
 
250
            //we get a multiplication factor of 1
 
251
            return MathUtils.clamp(1.0 / maxPosRatio * scrollbar.flickableItem.visibleArea[propPosRatio]
 
252
                                   * (draggableLength - scrollbar.__styleInstance.thumb[propSize]) + margin, min, max);
117
253
        }
118
254
 
119
255
        /*!
120
 
          \internal
121
256
          Calculates the slider size for ListViews based on the visible area's position
122
257
          and size ratios, clamping it between min and max.
123
258
 
124
259
          The function can be used in Scrollbar styles to calculate the size of the slider.
 
260
 
 
261
          NOTE: THIS CODE IS ASSUMING THAT "MAX" IS ALSO THE "SIZE" OF THE TROUGH THAT THE
 
262
          THUMB CAN MOVE INTO! (which is what you want in 99.9% of the cases, for a scrollbar)
125
263
          */
126
 
        function sliderSize(min, max) {
127
 
            var sizeRatio = styledItem.flickableItem.visibleArea[propSizeRatio];
128
 
            var posRatio = styledItem.flickableItem.visibleArea[propPosRatio];
 
264
        function sliderSize(scrollbar, min, max) {
 
265
            var sizeRatio = scrollbar.flickableItem.visibleArea[propSizeRatio];
 
266
            var posRatio = scrollbar.flickableItem.visibleArea[propPosRatio];
 
267
 
 
268
            //(sizeRatio * max) is the current ideal size, as recommended by Flickable visibleArea props
129
269
            var sizeUnderflow = (sizeRatio * max) < min ? min - (sizeRatio * max) : 0
 
270
 
 
271
            //we multiply by (max - sizeUndeflow) because we want to simulate a shorter trough. This is because
 
272
            //posRatio value is [0...1-sizeRatio] so it assumes the slider will be of size sizeRatio*size_of_the_trough
 
273
            //(because that's the only way to make the slider fill the remaining part of the trough when posRatio is
 
274
            //at its maximum value), while our slider could actually be bigger due to the imposed "min" value.
 
275
            //We will compensate for this shift by adding sizeUnderflow to endPos.
130
276
            var startPos = posRatio * (max - sizeUnderflow)
131
277
            var endPos = (posRatio + sizeRatio) * (max - sizeUnderflow) + sizeUnderflow
132
278
            var overshootStart = startPos < 0 ? -startPos : 0
136
282
            var adjustedStartPos = startPos + overshootStart
137
283
            var adjustedEndPos = endPos - overshootStart - overshootEnd
138
284
 
139
 
            // final position and size of thumb
 
285
            // final position and size of slider
140
286
            var position = adjustedStartPos + min > max ? max - min : adjustedStartPos
141
287
            var result = (adjustedEndPos - position) < min ? min : (adjustedEndPos - position)
142
288
 
144
290
        }
145
291
 
146
292
        /*!
147
 
          \internal
148
293
          The function calculates and clamps the position to be scrolled to the minimum
149
294
          and maximum values.
150
295
 
152
297
          size set (meaning the minimum is set to 0.0). Implementations should consider
153
298
          using an invisible cursor to drag the slider and the ListView position.
154
299
          */
155
 
        function scrollAndClamp(amount, min, max) {
156
 
            return styledItem.flickableItem[propOrigin] +
157
 
                    MathUtils.clamp(styledItem.flickableItem[propContent] - styledItem.flickableItem[propOrigin] + amount,
158
 
                          min, max);
 
300
        function scrollAndClamp(scrollbar, amount, min, max) {
 
301
            return scrollbar.flickableItem[propOrigin] +
 
302
                    MathUtils.clamp(scrollbar.flickableItem[propContent]
 
303
                                    - scrollbar.flickableItem[propOrigin] + amount,
 
304
                                    min, max);
159
305
        }
160
306
 
161
307
        /*!
162
 
          \internal
163
308
          The function calculates the new position of the dragged slider. The amount is
164
309
          relative to the contentSize, which is either the flickable's contentHeight or
165
310
          contentWidth or other calculated value, depending on its orientation. The pageSize
166
311
          specifies the visibleArea, and it is usually the heigtht/width of the scrolling area.
 
312
 
 
313
          //FIXME: should we change the API and remove pageSize or just pass trough's size as pageSize par?
 
314
 
 
315
          NOTE: when flickable.topMargin is 5GU, contentY has to be -5GU (not 0!) to be at the top of the scrollable!!
167
316
          */
168
 
        function dragAndClamp(cursor, contentSize, pageSize) {
169
 
            styledItem.flickableItem[propContent] =
170
 
                    styledItem.flickableItem[propOrigin] + cursor[propCoordinate] * contentSize / pageSize;
171
 
        }
172
 
    }
173
 
 
174
 
    /*****************************************
175
 
      Visuals
176
 
     *****************************************/
177
 
    anchors.fill: parent
178
 
 
179
 
    opacity: overlayOpacityWhenHidden
180
 
    state: {
181
 
        if (!isScrollable)
182
 
            return '';
183
 
        else if (overlay) {
184
 
            if (flickableItem.moving)
185
 
                return 'overlay';
186
 
            else
187
 
                return 'stopped';
188
 
        } else
189
 
            return 'shown';
 
317
        function dragAndClamp(scrollbar, relThumbPosition, contentSize, pageSize) {
 
318
            scrollbar.flickableItem[propContent] =
 
319
                    scrollbar.flickableItem[propOrigin] + relThumbPosition * (contentSize - scrollbar.flickableItem[propSize]) - leadingContentMargin; //don't use pageSize, we don't know if the scrollbar is edge to edge!;
 
320
        }
 
321
    }
 
322
 
 
323
    // The thumb appears whenever the mouse gets close enough to the scrollbar
 
324
    // and disappears after being for a long enough time far enough of it
 
325
    MouseArea {
 
326
        id: proximityArea
 
327
 
 
328
        //like containsMouse, but unlike it this property is not true when the user
 
329
        //taps on the area using touchscreen
 
330
        property bool containsMouseDevice: false
 
331
 
 
332
        anchors.fill: parent
 
333
        propagateComposedEvents: true
 
334
        preventStealing: false
 
335
        enabled: isScrollable && interactive
 
336
        hoverEnabled: isScrollable && interactive
 
337
        Mouse.ignoreSynthesizedEvents: true
 
338
        Mouse.onEntered: {
 
339
            isMouseConnected = true
 
340
            proximityArea.containsMouseDevice = true
 
341
        }
 
342
        Mouse.onExited: {
 
343
            overshootTimer.restart();
 
344
            proximityArea.containsMouseDevice = false
 
345
        }
 
346
        onPressed: { mouse.accepted = false }
 
347
 
 
348
        Timer {
 
349
            id: overshootTimer
 
350
            interval: scrollbarThicknessAnimation.duration * 10
 
351
        }
 
352
    }
 
353
 
 
354
    //each scrollbar connects to both width and height because
 
355
    //we want to show both the scrollbar in both cases
 
356
    Connections {
 
357
        target: (flickableItem && initialized) ? flickableItem : null
 
358
        onContentHeightChanged: flashScrollbar()
 
359
        onHeightChanged: flashScrollbar()
 
360
        onContentWidthChanged: flashScrollbar()
 
361
        onWidthChanged: flashScrollbar()
 
362
    }
 
363
 
 
364
    SmoothedAnimation {
 
365
        id: scrollAnimation
 
366
        //duration and easing coming from UX spec
 
367
        duration: UbuntuAnimation.SlowDuration
 
368
        easing.type: Easing.InOutCubic
 
369
        target: styledItem.flickableItem
 
370
        property: (isVertical) ? "contentY" : "contentX"
 
371
        //when the listview has variable size delegates the contentHeight estimation done by ListView
 
372
        //could make us overshoot, especially when going from top to bottom of the list or viceversa.
 
373
        //We call returnToBounds to mitigate that.
 
374
        onRunningChanged: if (!running && flickableItem) {
 
375
                              flickableItem.returnToBounds()
 
376
                          }
 
377
    }
 
378
 
 
379
    Flow {
 
380
        id: flowContainer
 
381
        property bool showSteppers: false
 
382
        property bool showTrough: false
 
383
        property bool showCornerRect: false
 
384
        //thickness of trough and thumb
 
385
        property real thickness: 0
 
386
        property real thumbThickness: 0
 
387
 
 
388
        property real proximityThickness: (isVertical) ? styledItem.width - thickness
 
389
                                                       : styledItem.height - thickness
 
390
        anchors {
 
391
            fill: parent
 
392
            leftMargin: (!isVertical || frontAligned) ? 0.0 : flowContainer.proximityThickness
 
393
            rightMargin: (!isVertical || rearAligned) ? 0.0 : flowContainer.proximityThickness
 
394
            topMargin: (isVertical || topAligned) ? 0.0 : flowContainer.proximityThickness
 
395
            bottomMargin: (isVertical || bottomAligned) ? 0.0 : flowContainer.proximityThickness
 
396
        }
 
397
        clip: true
 
398
        flow: (isVertical) ? Flow.TopToBottom : Flow.LeftToRight
 
399
 
 
400
        Item {
 
401
            id: troughContainer
 
402
            //account for 2 steppers at the end of the scrollbar
 
403
            width: isVertical ? parent.width
 
404
                              : parent.width - steppersMouseArea.width
 
405
            height: isVertical ? parent.height - steppersMouseArea.height
 
406
                               : parent.height
 
407
 
 
408
            // represents the visible area of the scrollbar where
 
409
            //slider and thumb connector are placed
 
410
            Rectangle {
 
411
                id: trough
 
412
                anchors.fill: parent
 
413
                visible: flowContainer.showTrough
 
414
            }
 
415
 
 
416
            Rectangle {
 
417
                id: slider
 
418
                objectName: "interactiveScrollbarThumb"
 
419
 
 
420
                //this property is true if the user could be starting a drag (i.e. pressed inside the thumb)
 
421
                //but drag.active is not true yet (because that requires dragging >0 pixels)
 
422
                //Usecase: thumb is at the top, user drags towards the top and
 
423
                //goes out of window -> drag.active won't trigger because the thumb can't move by even 1px,
 
424
                //but we still want the logic to know that user could be trying to drag (by moving the
 
425
                //mouse down again afterwards, for example)
 
426
                //
 
427
                //NOTE: starting a drag while the scrollbars are in steppers mode is considered a mouse drag
 
428
                //(because we want the style to stay to steppers) even when started with touch!
 
429
                property bool mouseDragging: false
 
430
                property bool touchDragging: false
 
431
 
 
432
                //the proportional position of the thumb relative to the trough it can move into
 
433
                //It is 0 when the thumb is at its minimum value, and 1 when it is at its maximum value
 
434
                property real relThumbPosition : isVertical
 
435
                                                 ? (slider.y - thumbsExtremesMargin) / (trough.height - 2*thumbsExtremesMargin - slider.height)
 
436
                                                 : (slider.x - thumbsExtremesMargin) / (trough.width - 2*thumbsExtremesMargin - slider.width)
 
437
 
 
438
                //we just need to call this onPressed, so we shouldn't use a binding for it, which would get
 
439
                //reevaluated any time one of the properties changes.
 
440
                //+ having it as a binding has the sideeffect that when we query its value from inside onPressed
 
441
                //it may not be using the most up-to-date value for *all* the variables it uses, and that would
 
442
                //break the logic
 
443
                function containsTouch() {
 
444
                    var touchX = slider.mapFromItem(thumbArea, thumbArea.mouseX, thumbArea.mouseY).x
 
445
                    var touchY = slider.mapFromItem(thumbArea, thumbArea.mouseX, thumbArea.mouseY).y
 
446
                    //relaxed checks on the position of the touch, to allow some error
 
447
                    if (touchX >= -visuals.touchDragStartMargin
 
448
                            && touchX <= slider.width + visuals.touchDragStartMargin
 
449
                            && touchY >= -visuals.touchDragStartMargin
 
450
                            && touchY <= slider.height + visuals.touchDragStartMargin) {
 
451
                        return true
 
452
                    } else {
 
453
                        return false
 
454
                    }
 
455
                }
 
456
                function scroll(amount) {
 
457
                    scrollAnimation.to = scrollbarUtils.scrollAndClamp(styledItem, amount, -leadingContentMargin,
 
458
                                                                       Math.max(contentSize + trailingContentMargin - pageSize, -leadingContentMargin));
 
459
                    scrollAnimation.restart();
 
460
                }
 
461
                function drag() {
 
462
                    scrollbarUtils.dragAndClamp(styledItem, slider.relThumbPosition, totalContentSize, pageSize);
 
463
                }
 
464
 
 
465
                anchors {
 
466
                    verticalCenter: (isVertical) ? undefined : trough.verticalCenter
 
467
                    horizontalCenter: (isVertical) ? trough.horizontalCenter : undefined
 
468
                }
 
469
                x: (isVertical) ? 0 : scrollbarUtils.sliderPos(styledItem, thumbsExtremesMargin, trough.width - slider.width - thumbsExtremesMargin)
 
470
                y: (!isVertical) ? 0 : scrollbarUtils.sliderPos(styledItem, thumbsExtremesMargin, trough.height - slider.height - thumbsExtremesMargin)
 
471
                radius: visuals.sliderRadius
 
472
                color: Qt.rgba(sliderColor.r, sliderColor.g, sliderColor.b,
 
473
                               sliderColor.a * (visuals.draggingThumb ? 1.0 : 0.6))
 
474
 
 
475
                //This is to stop the scrollbar from changing size while being dragged when we have listviews
 
476
                //with delegates of variable size (in those cases, contentWidth/height changes as the user scrolls
 
477
                //because of the way ListView estimates the size of the out-of-views delegates
 
478
                //and that would trigger resizing of the thumb)
 
479
                //NOTE: otoh, we want the x/y binding to apply (even though it will cause a small flickering occasionally)
 
480
                //even while dragging, because that guarantees the view is at the top when the user drags to the top
 
481
                Binding {
 
482
                    when: !visuals.draggingThumb
 
483
                    target: slider
 
484
                    property: "width"
 
485
                    value: (isVertical) ? flowContainer.thumbThickness
 
486
                                        : scrollbarUtils.sliderSize(styledItem, visuals.minimumSliderSize, trough.width - 2 * thumbsExtremesMargin)
 
487
                }
 
488
                Binding {
 
489
                    when: !visuals.draggingThumb
 
490
                    target: slider
 
491
                    property: "height"
 
492
                    value: (!isVertical) ? flowContainer.thumbThickness
 
493
                                         : scrollbarUtils.sliderSize(styledItem, visuals.minimumSliderSize, trough.height - 2 * thumbsExtremesMargin)
 
494
                }
 
495
            }
 
496
 
 
497
            //we reuse the MouseArea for touch interactions as well, because MultiTouchPointArea
 
498
            //can't propagate touch events underneath, if not consumed
 
499
            //(it would also require implementing drag as it doesn't have it)
 
500
            MouseArea {
 
501
                id: thumbArea
 
502
 
 
503
                property real originXAtDragStart: 0
 
504
                property real originYAtDragStart: 0
 
505
                property real contentXAtDragStart: 0
 
506
                property real contentYAtDragStart: 0
 
507
 
 
508
                //cache input device position in order to ignore mouse/touch
 
509
                //moves which are shorter than a threshold. This is needed due
 
510
                //to the low precision of some touchscreen devices, to avoid
 
511
                //shaking the view while slowly dragging via touch
 
512
                property real previousX: 0
 
513
                property real previousY: 0
 
514
 
 
515
                //following UX spec, if during a drag the mouse goes far away from the scrollbar
 
516
                //we want to temporarily lock the drag and reset the scrollbar to where it was when
 
517
                //the drag started, WITHOUT losing the drag (if the mouse gets back within the distance
 
518
                //threshold, the drag has to resum without the need to release and pressHold again)
 
519
                property bool lockDrag: false
 
520
 
 
521
                property string scrollingProp: isVertical ? "y" : "x"
 
522
                property string sizeProp: isVertical ? "height" : "width"
 
523
 
 
524
                function initDrag(isMouse) {
 
525
                    __disableStateBinding = true
 
526
                    thumbArea.saveFlickableScrollingState()
 
527
                    if (isMouse) {
 
528
                        slider.mouseDragging = true
 
529
                        visuals.state = "steppers"
 
530
                    } else {
 
531
                        slider.touchDragging = true
 
532
                        visuals.state = "thumb"
 
533
                    }
 
534
                }
 
535
 
 
536
                function resetDrag() {
 
537
                    if (thumbArea.lockDrag) thumbArea.lockDrag = false
 
538
                    slider.mouseDragging = false
 
539
                    slider.touchDragging = false
 
540
                    __disableStateBinding = false
 
541
                }
 
542
 
 
543
                function handlePress(mouseX, mouseY) {
 
544
                    if (!thumbArea.pressed) return
 
545
 
 
546
                    var mouseScrollingProp = isVertical ? mouseY : mouseX
 
547
                    if (mouseScrollingProp < slider[scrollingProp]) {
 
548
                        slider.scroll(-pageSize*visuals.longScrollingRatio)
 
549
                    } else if (mouseScrollingProp > slider[scrollingProp] + slider[sizeProp]) {
 
550
                        slider.scroll(pageSize*visuals.longScrollingRatio)
 
551
                    }
 
552
                }
 
553
 
 
554
                function saveFlickableScrollingState() {
 
555
                    originXAtDragStart = flickableItem.originX
 
556
                    originYAtDragStart = flickableItem.originY
 
557
                    contentXAtDragStart = flickableItem.contentX
 
558
                    contentYAtDragStart = flickableItem.contentY
 
559
                }
 
560
 
 
561
                function resetFlickableToPreDragState() {
 
562
                    //NOTE: when dealing with ListViews which have
 
563
                    //delegates that are highly variable in size originX/Y and contentWidth/Height
 
564
                    //change based on "out-of-view delegates" size estimation done by ListView.
 
565
                    //In that case, we can't do anything to bring back the view to where it was
 
566
                    //when the drag started, so we'd better not do anything instead of doing something
 
567
                    //which we're sure will be wrong
 
568
                    if (originXAtDragStart != flickableItem.originX
 
569
                            || originYAtDragStart != flickableItem.originY) {
 
570
                        return
 
571
                    }
 
572
                    flickableItem.contentX = contentXAtDragStart
 
573
                    flickableItem.contentY = contentYAtDragStart
 
574
                }
 
575
 
 
576
                function cacheMousePosition(mouse) {
 
577
                    previousX = mouse.x
 
578
                    previousY = mouse.y
 
579
                }
 
580
 
 
581
                anchors {
 
582
                    fill: trough
 
583
                    // set margins adding 2 dp for error area
 
584
                    leftMargin: (!isVertical || frontAligned) ? 0 : units.dp(-2)
 
585
                    rightMargin: (!isVertical || rearAligned) ? 0 : units.dp(-2)
 
586
                    topMargin: (isVertical || topAligned) ?  0 : units.dp(-2)
 
587
                    bottomMargin: (isVertical || bottomAligned) ?  0 : units.dp(-2)
 
588
                }
 
589
                enabled: isScrollable && interactive
 
590
                onPressed: {
 
591
                    cacheMousePosition(mouse)
 
592
                    //potentially we allow using touch to trigger mouse interactions, in case the
 
593
                    //mouse has previously hovered over the area and activated the steppers style
 
594
                    //checking for the value of visuals.state wouldn't be useful here, the value could be
 
595
                    //"hidden" even if the values have only just started transitioning from "steppers" state
 
596
                    if (trough.visible) {
 
597
                        handlePress(mouse.x, mouse.y)
 
598
                        //don't start the press and hold timer to avoid conflicts with the drag
 
599
                        var mappedCoord = mapToItem(slider, mouseX, mouseY)
 
600
                        if (!slider.contains(Qt.point(mappedCoord.x, mappedCoord.y))) {
 
601
                            pressHoldTimer.startTimer(thumbArea)
 
602
                        } else {
 
603
                            //we can't tell whether the drag is started from mouse or touch
 
604
                            //(unless we add an additional multipointtoucharea and reimplement drag
 
605
                            //but MPTArea can't ignore touch events so we wouldn't be able to propagate those)
 
606
                            //so we assume that it's a mouse drag if the mouse is within the proximity area
 
607
                            //when the mouse is dragged
 
608
                            initDrag(true)
 
609
                        }
 
610
                    } else if (visuals.veryLongContentItem && slider.containsTouch()) {
 
611
                        initDrag(false)
 
612
                    } else {
 
613
                        //propagate otherwise
 
614
                        mouse.accepted = false
 
615
                    }
 
616
                }
 
617
                onPositionChanged: {
 
618
                    //avoid shaking the view when the user is draggin via touch
 
619
                    if (Math.abs(mouse.x - previousX) < units.dp(2)
 
620
                            && Math.abs(mouse.y - previousY) < units.dp(2)) {
 
621
                        return
 
622
                    }
 
623
                    cacheMousePosition(mouse)
 
624
 
 
625
                    if (pressedButtons == Qt.LeftButton && (slider.mouseDragging || slider.touchDragging)) {
 
626
                        if ((isVertical && Math.abs(mouse.x - thumbArea.x) >= flowContainer.thickness * 10)
 
627
                                || (!isVertical && Math.abs(mouse.y - thumbArea.y) >= flowContainer.thickness * 10)) {
 
628
                            //don't reset it if the user is already scrolling the view (via keyboard or similar)
 
629
                            if (!scrollAnimation.running) {
 
630
                                resetFlickableToPreDragState()
 
631
                                thumbArea.lockDrag = true
 
632
                            }
 
633
                        } else {
 
634
                            //don't clash with scrolling animation
 
635
                            if (scrollAnimation.running) scrollAnimation.stop()
 
636
 
 
637
                            if (thumbArea.lockDrag) thumbArea.lockDrag = false
 
638
                            slider.drag()
 
639
                        }
 
640
                    }
 
641
                }
 
642
                onCanceled: {
 
643
                    resetDrag()
 
644
                    pressHoldTimer.stop()
 
645
                }
 
646
                onReleased: {
 
647
                    resetDrag()
 
648
                    pressHoldTimer.stop()
 
649
                }
 
650
                drag {
 
651
                    //don't start a drag while we're scrolling using press and hold
 
652
                    target: pressHoldTimer.running ? undefined : slider
 
653
                    axis: (isVertical) ? Drag.YAxis : Drag.XAxis
 
654
                    minimumY: lockDrag ? slider.y : thumbsExtremesMargin
 
655
                    maximumY: lockDrag ? slider.y : trough.height - slider.height - thumbsExtremesMargin
 
656
                    minimumX: lockDrag ? slider.x : thumbsExtremesMargin
 
657
                    maximumX: lockDrag ? slider.x : trough.width - slider.width - thumbsExtremesMargin
 
658
                    onActiveChanged: {
 
659
                        if (drag.active) {
 
660
                            //drag.active is true only when the target has been moved by >0 pixels (if threshold == 0)
 
661
                            //that means if the bar is at the top and user drags it upwards, drag.active won't change,
 
662
                            //but the user will still be able to leave the app window from above, move mouse to the left (or right)
 
663
                            //enough to trigger the drag lock logic, and then come back in the window and move downwards.
 
664
                            //At *that* point drag.active will become true, but we don't want to actually drag the thumb
 
665
                            //because the mouse is still outside the area where thumb is follow input device movements
 
666
                            if (!lockDrag) {
 
667
                                slider.drag()
 
668
                            }
 
669
                        } else {
 
670
                            resetDrag()
 
671
                        }
 
672
                    }
 
673
 
 
674
                    //NOTE: we need threshold to be 0, otherwise it will be
 
675
                    //impossible to drag contentItems which have a size so that
 
676
                    //"flickableItem.height - contentItem.height < threshold"!!!
 
677
                    threshold: 0
 
678
                }
 
679
 
 
680
                Timer {
 
681
                    id: pressHoldTimer
 
682
 
 
683
                    //This var is needed to reuse the same timer to handle
 
684
                    //both thumb and steppers press-and-hold
 
685
                    //NOTE: the item MUST provide a handlePress method
 
686
                    property MouseArea startedBy
 
687
                    property bool firstTrigger: true
 
688
 
 
689
                    function startTimer(item) {
 
690
                        startedBy = item
 
691
                        start()
 
692
                    }
 
693
 
 
694
                    interval: firstTrigger ? 500 : 250
 
695
                    onTriggered: {
 
696
                        if (firstTrigger) firstTrigger = false
 
697
                        if (startedBy !== undefined) {
 
698
                            startedBy.handlePress(startedBy.mouseX, startedBy.mouseY)
 
699
                        } else {
 
700
                            console.log("Congrats, you have just found a bug! Press and hold timer is running without knowing the item that started it. Please report this.")
 
701
                        }
 
702
                    }
 
703
                    repeat: true
 
704
                    onRunningChanged: {
 
705
                        if (!running) {
 
706
                            firstTrigger = true
 
707
                        }
 
708
                    }
 
709
                }
 
710
            }
 
711
        }
 
712
        MouseArea {
 
713
            id: steppersMouseArea
 
714
            //size is handled by the states
 
715
 
 
716
            function handlePress() {
 
717
                var mappedCoordFirstStepper = mapToItem(firstStepper, mouseX, mouseY)
 
718
                var mappedCoordSecondStepper = mapToItem(secondStepper, mouseX, mouseY)
 
719
 
 
720
                if (firstStepper.contains(Qt.point(mappedCoordFirstStepper.x, mappedCoordFirstStepper.y))) {
 
721
                    slider.scroll(-pageSize * visuals.shortScrollingRatio)
 
722
                } else if (secondStepper.contains(Qt.point(mappedCoordSecondStepper.x, mappedCoordSecondStepper.y))) {
 
723
                    slider.scroll(pageSize * visuals.shortScrollingRatio)
 
724
                }
 
725
            }
 
726
 
 
727
            enabled: isScrollable && interactive
 
728
            visible: flowContainer.showSteppers
 
729
 
 
730
            //We don't change the size of the images because we let the image reader figure the size out,
 
731
            //though that means we have to hide the images somehow while the mousearea is visible but has
 
732
            //null size. We choose to enable clipping here instead of creating bindings on images' visible prop
 
733
            clip: true
 
734
            onPressed: {
 
735
                handlePress()
 
736
                pressHoldTimer.startTimer(steppersMouseArea)
 
737
            }
 
738
            onReleased: pressHoldTimer.stop()
 
739
            onCanceled: pressHoldTimer.stop()
 
740
 
 
741
            Rectangle  {
 
742
                id: firstStepper
 
743
                anchors {
 
744
                    //it's left in both cases, otherwise using RTL
 
745
                    //would break the layout of the arrow
 
746
                    left: parent.left
 
747
                    right: isVertical ? parent.right : undefined
 
748
                    top: parent.top
 
749
                    bottom: !isVertical ? parent.bottom : undefined
 
750
                }
 
751
                color: trough.color
 
752
                visible: parent.visible
 
753
                clip: true
 
754
 
 
755
                Binding {
 
756
                    target: firstStepper
 
757
                    property: "height"
 
758
                    when: isVertical
 
759
                    value: visible ? troughThicknessSteppersStyle : 0
 
760
                }
 
761
                Binding {
 
762
                    target: firstStepper
 
763
                    property: "width"
 
764
                    when: !isVertical
 
765
                    value: visible ? troughThicknessSteppersStyle : 0
 
766
                }
 
767
                Image {
 
768
                    anchors.centerIn: parent
 
769
                    rotation: isVertical ? 180 : 90
 
770
                    source: Qt.resolvedUrl("../artwork/scrollbar_arrow.png")
 
771
                    cache: true
 
772
                }
 
773
            }
 
774
            Rectangle {
 
775
                id: secondStepper
 
776
                anchors {
 
777
                    left: isVertical ? parent.left : firstStepper.right
 
778
                    right: isVertical ? parent.right : undefined
 
779
                    top: !isVertical ? parent.top : firstStepper.bottom
 
780
                    bottom: !isVertical ? parent.bottom : undefined
 
781
                }
 
782
                color: trough.color
 
783
                clip: true
 
784
                visible: parent.visible
 
785
 
 
786
                Binding {
 
787
                    target: secondStepper
 
788
                    property: "height"
 
789
                    when: isVertical
 
790
                    value: visible ? troughThicknessSteppersStyle : 0
 
791
                }
 
792
                Binding {
 
793
                    target: secondStepper
 
794
                    property: "width"
 
795
                    when: !isVertical
 
796
                    value: visible ? troughThicknessSteppersStyle : 0
 
797
                }
 
798
                Image {
 
799
                    anchors.centerIn: parent
 
800
                    fillMode: Image.PreserveAspectFit
 
801
                    rotation: isVertical ? 0 : -90
 
802
                    source: Qt.resolvedUrl("../artwork/scrollbar_arrow.png")
 
803
                    cache: true
 
804
                }
 
805
            }
 
806
        }
 
807
    }
 
808
 
 
809
    //just a hack: a rectangle which covers the corner where scrollbars meet, when they're in steppers style
 
810
    Rectangle {
 
811
        id: cornerRect
 
812
        anchors.left: {
 
813
            if (styledItem.__buddyScrollbar && styledItem.__buddyScrollbar.__styleInstance
 
814
                    && styledItem.__buddyScrollbar.__styleInstance.isScrollable) {
 
815
                if (isVertical) {
 
816
                    return flowContainer.left
 
817
                } else {
 
818
                    if (styledItem.__buddyScrollbar.align === Qt.AlignTrailing) {
 
819
                        return flowContainer.right
 
820
                    } else
 
821
                        return undefined
 
822
                }
 
823
            }
 
824
        }
 
825
        anchors.right: {
 
826
            if (styledItem.__buddyScrollbar && styledItem.__buddyScrollbar.__styleInstance
 
827
                    && styledItem.__buddyScrollbar.__styleInstance.isScrollable) {
 
828
                if (isVertical) {
 
829
                    return flowContainer.right
 
830
                } else {
 
831
                    if (styledItem.__buddyScrollbar.align === Qt.AlignLeading) {
 
832
                        return flowContainer.left
 
833
                    } else {
 
834
                        return undefined
 
835
                    }
 
836
                }
 
837
            }
 
838
        }
 
839
        anchors.top: {
 
840
            if (styledItem.__buddyScrollbar && styledItem.__buddyScrollbar.__styleInstance
 
841
                    && styledItem.__buddyScrollbar.__styleInstance.isScrollable) {
 
842
                if (isVertical) {
 
843
                    if (styledItem.__buddyScrollbar.align === Qt.AlignBottom) {
 
844
                        return flowContainer.bottom
 
845
                    } else {
 
846
                        return undefined
 
847
                    }
 
848
                } else {
 
849
                    return undefined
 
850
                }
 
851
            }
 
852
        }
 
853
        anchors.bottom: {
 
854
            if (styledItem.__buddyScrollbar && styledItem.__buddyScrollbar.__styleInstance
 
855
                    && styledItem.__buddyScrollbar.__styleInstance.isScrollable) {
 
856
                if (isVertical) {
 
857
                    if (styledItem.__buddyScrollbar.align === Qt.AlignTop) {
 
858
                        return flowContainer.top
 
859
                    } else {
 
860
                        return undefined
 
861
                    }
 
862
                } else {
 
863
                    return flowContainer.bottom
 
864
                }
 
865
            }
 
866
        }
 
867
        //REMEMBER: the thickness animates during state changes,
 
868
        //so we want to bind the right side to the thickness of the scrollbar
 
869
        width: isVertical ? flowContainer.thickness : troughThicknessSteppersStyle
 
870
        height: isVertical ? troughThicknessSteppersStyle : flowContainer.thickness
 
871
        color: trough.color
 
872
        visible: flowContainer.showCornerRect
 
873
                 && styledItem.__buddyScrollbar
 
874
                 && styledItem.__buddyScrollbar.__styleInstance.isScrollable
 
875
    }
 
876
 
 
877
    Binding {
 
878
        when: !__disableStateBinding
 
879
        target: visuals
 
880
        property: 'state'
 
881
        value: {
 
882
            if (!isScrollable && !alwaysOnScrollbars)
 
883
                return '';
 
884
            else if (overlay) {
 
885
                //we use the check on running to avoid to make it so that the scrollbar completes the transition
 
886
                //to the steppers (or thumb) state even when the mouse exits the area prematurely (e.g. when the mouse
 
887
                //overshoots, without the .running check the scrollbar would stop the growing transition halfway and
 
888
                //go back to hidden mode)
 
889
                if (proximityArea.containsMouseDevice || (draggingThumb && slider.mouseDragging) || __overshootTimer.running) {
 
890
                    return 'steppers'
 
891
                } else if (thumbStyleFlag || (draggingThumb && slider.touchDragging) || __overshootTimer.running) {
 
892
                    return 'thumb'
 
893
                } else if (flickableItem.moving || scrollAnimation.running || transitionToIndicator.running) {
 
894
                    return 'indicator';
 
895
                } else return 'hidden';
 
896
            } else {
 
897
                return 'steppers'
 
898
            }
 
899
        }
190
900
    }
191
901
 
192
902
    states: [
193
903
        State {
194
 
            name: 'stopped'
 
904
            name: 'hidden'
195
905
            extend: ''
196
906
            PropertyChanges {
197
907
                target: visuals
199
909
            }
200
910
        },
201
911
        State {
202
 
            name: "shown"
203
 
            PropertyChanges {
204
 
                target: visuals
205
 
                opacity: overlayOpacityWhenShown
206
 
            }
207
 
        },
208
 
        State {
209
 
            name: 'overlay'
210
 
            PropertyChanges {
211
 
                target: visuals
212
 
                opacity: overlayOpacityWhenShown
 
912
            name: 'shown'
 
913
            PropertyChanges {
 
914
                target: visuals
 
915
                opacity: overlayOpacityWhenShown
 
916
            }
 
917
        },
 
918
        State {
 
919
            name: 'indicator'
 
920
            extend: 'shown'
 
921
            PropertyChanges {
 
922
                target: flowContainer
 
923
                thickness: troughThicknessIndicatorStyle
 
924
                thumbThickness: indicatorThickness
 
925
                showSteppers: false
 
926
                showTrough: alwaysOnScrollbars
 
927
                showCornerRect: false
 
928
            }
 
929
            PropertyChanges {
 
930
                target: steppersMouseArea
 
931
                width: 0
 
932
                height: 0
 
933
            }
 
934
            PropertyChanges {
 
935
                target: trough
 
936
                color: Qt.rgba(troughColorThumbStyle.r,
 
937
                               troughColorThumbStyle.g,
 
938
                               troughColorThumbStyle.b,
 
939
                               troughColorThumbStyle.a * 0.2)
 
940
            }
 
941
        },
 
942
        State {
 
943
            name: 'thumb'
 
944
            extend: 'shown'
 
945
            PropertyChanges {
 
946
                target: flowContainer
 
947
                thickness: visuals.troughThicknessThumbStyle
 
948
                thumbThickness: visuals.thumbThickness
 
949
                showSteppers: false
 
950
                showTrough: alwaysOnScrollbars
 
951
                showCornerRect: false
 
952
            }
 
953
            PropertyChanges {
 
954
                target: steppersMouseArea
 
955
                width: 0
 
956
                height: 0
 
957
            }
 
958
            PropertyChanges {
 
959
                target: trough
 
960
                color: Qt.rgba(troughColorThumbStyle.r,
 
961
                               troughColorThumbStyle.g,
 
962
                               troughColorThumbStyle.b,
 
963
                               troughColorThumbStyle.a * 0.2)
 
964
            }
 
965
        },
 
966
        State {
 
967
            name: 'steppers'
 
968
            extend: 'shown'
 
969
            PropertyChanges {
 
970
                target: flowContainer
 
971
                thickness: visuals.troughThicknessSteppersStyle
 
972
                thumbThickness: visuals.thumbThickness
 
973
                showSteppers: visuals.useSteppers
 
974
                showTrough: true
 
975
                showCornerRect: true
 
976
            }
 
977
            PropertyChanges {
 
978
                target: steppersMouseArea
 
979
                width: isVertical ? flowContainer.width
 
980
                                  : (firstStepper.visible ? firstStepper.width : 0)
 
981
                                    + (secondStepper.visible ? secondStepper.width : 0)
 
982
                height: isVertical ? (firstStepper.visible ? firstStepper.height : 0)
 
983
                                     + (secondStepper.visible ? secondStepper.height : 0)
 
984
                                   : trough.height
 
985
            }
 
986
            PropertyChanges {
 
987
                target: trough
 
988
                color: Qt.rgba(troughColorSteppersStyle.r,
 
989
                               troughColorSteppersStyle.g,
 
990
                               troughColorSteppersStyle.b,
 
991
                               troughColorSteppersStyle.a * 0.9)
213
992
            }
214
993
        }
215
994
    ]
225
1004
            }
226
1005
        },
227
1006
        Transition {
228
 
            from: '*'
229
 
            to: 'overlay'
230
 
            NumberAnimation {
231
 
                target: visuals
232
 
                property: "opacity"
233
 
                duration: scrollbarFadeInAnimation.duration
234
 
                easing: scrollbarFadeInAnimation.easing
235
 
            }
236
 
        },
237
 
        Transition {
238
 
            from: "overlay"
239
 
            to: "stopped"
240
 
            SequentialAnimation {
241
 
                PauseAnimation { duration: scrollbarFadeOutPause }
 
1007
            from: 'thumb,steppers'
 
1008
            to: 'indicator'
 
1009
            id: collapsingTransition
 
1010
            SequentialAnimation {
 
1011
                //don't pause the animation if we're showing the *hint* and the hintingStyle changes from
 
1012
                //thumb/steppers to indicator
 
1013
                PauseAnimation { id: scrollbarCollapseAnim; duration: __disableStateBinding ? 0 : scrollbarCollapsePause }
 
1014
                PropertyAction {
 
1015
                    target: steppersMouseArea
 
1016
                    properties: isVertical ? "width" : "height"
 
1017
                }
 
1018
                ParallelAnimation {
 
1019
                    NumberAnimation {
 
1020
                        target: flowContainer
 
1021
                        properties: "thickness,thumbThickness"
 
1022
                        duration: scrollbarThicknessAnimation.duration
 
1023
                        easing: scrollbarThicknessAnimation.easing
 
1024
                    }
 
1025
                    NumberAnimation {
 
1026
                        target: steppersMouseArea
 
1027
                        properties: isVertical ? "height" : "width"
 
1028
                        duration: scrollbarThicknessAnimation.duration
 
1029
                        easing: scrollbarThicknessAnimation.easing
 
1030
                    }
 
1031
                    ColorAnimation { target: trough; duration: scrollbarThicknessAnimation.duration }
 
1032
                }
 
1033
                PropertyAction {
 
1034
                    target: flowContainer
 
1035
                    properties: "showSteppers,showTrough,showCornerRect"
 
1036
                }
 
1037
            }
 
1038
        },
 
1039
        Transition {
 
1040
            id: transitionToIndicator
 
1041
            from: '*'
 
1042
            to: 'indicator'
 
1043
            SequentialAnimation {
 
1044
                ParallelAnimation {
 
1045
                    NumberAnimation {
 
1046
                        target: visuals
 
1047
                        property: "opacity"
 
1048
                        duration: scrollbarThicknessAnimation.duration
 
1049
                        easing: scrollbarThicknessAnimation.easing
 
1050
                    }
 
1051
                    NumberAnimation {
 
1052
                        target: flowContainer
 
1053
                        properties: "thickness,thumbThickness"
 
1054
                        duration: scrollbarThicknessAnimation.duration
 
1055
                        easing: scrollbarThicknessAnimation.easing
 
1056
                    }
 
1057
                    ColorAnimation { target: trough; duration: scrollbarThicknessAnimation.duration }
 
1058
                }
 
1059
                //this covers the case when scrollbar is still transitioning from steppers style to hidden,
 
1060
                //but the transition is not yet finished, and we flick the view. At that point the state is "hidden"
 
1061
                //but the properties are still transitioning from steppers style to hidden. If a change to "indicator"
 
1062
                //style is triggered while the properties are still transitioning, we have to handle those properties
 
1063
                //as well to avoid immediate changes in the UI.
 
1064
                PropertyAction {
 
1065
                    target: flowContainer
 
1066
                    properties: "showSteppers,showTrough,showCornerRect"
 
1067
                }
 
1068
            }
 
1069
        },
 
1070
        Transition {
 
1071
            id: growingTransition
 
1072
            from: 'indicator'
 
1073
            to: 'thumb,steppers'
 
1074
            reversible: true
 
1075
            SequentialAnimation {
 
1076
                PropertyAction {
 
1077
                    target: flowContainer
 
1078
                    properties: "showSteppers,showTrough,showCornerRect"
 
1079
                }
 
1080
                PropertyAction {
 
1081
                    target: steppersMouseArea
 
1082
                    properties: isVertical ? "width" : "height"
 
1083
                }
 
1084
                ParallelAnimation {
 
1085
                    NumberAnimation {
 
1086
                        target: visuals
 
1087
                        property: "opacity"
 
1088
                        duration: scrollbarThicknessAnimation.duration
 
1089
                        easing: scrollbarThicknessAnimation.easing
 
1090
                    }
 
1091
                    NumberAnimation {
 
1092
                        target: flowContainer
 
1093
                        properties: "thickness,thumbThickness"
 
1094
                        duration: scrollbarThicknessAnimation.duration
 
1095
                        easing: scrollbarThicknessAnimation.easing
 
1096
                    }
 
1097
                    NumberAnimation {
 
1098
                        target: steppersMouseArea
 
1099
                        properties: isVertical ? "height" : "width"
 
1100
                        duration: scrollbarThicknessAnimation.duration
 
1101
                        easing: scrollbarThicknessAnimation.easing
 
1102
                    }
 
1103
                    ColorAnimation { target: trough; duration: scrollbarThicknessAnimation.duration }
 
1104
                }
 
1105
            }
 
1106
        },
 
1107
        Transition {
 
1108
            id: steppersTransition
 
1109
            from: '*'
 
1110
            to: 'thumb,steppers'
 
1111
            SequentialAnimation {
 
1112
                PropertyAction {
 
1113
                    target: flowContainer
 
1114
                    properties: "showSteppers,showTrough,showCornerRect"
 
1115
                }
 
1116
                PropertyAction {
 
1117
                    target: steppersMouseArea
 
1118
                    properties: isVertical ? "width" : "height"
 
1119
                }
 
1120
                ParallelAnimation {
 
1121
                    NumberAnimation {
 
1122
                        target: visuals
 
1123
                        property: "opacity"
 
1124
                        duration: scrollbarThicknessAnimation.duration
 
1125
                        easing: scrollbarThicknessAnimation.easing
 
1126
                    }
 
1127
                    NumberAnimation {
 
1128
                        target: flowContainer
 
1129
                        properties: "thickness,thumbThickness"
 
1130
                        //the check on opacity is needed because this transition is also run when a transition to "hidden"
 
1131
                        //is still in progress! For example: transition from indicator to hidden is started, the state is set to "hidden"
 
1132
                        //immediately, but the transition still has to happen. If, in this situation, we trigger a state change from "hidden"
 
1133
                        //to another state (thumb/steppers) this transition is fired, but the fact that state is "hidden" is not
 
1134
                        //really informative, we need to know if the stuff is actually visible on screen, because we want different
 
1135
                        //animation based on whether we're animating from visible or invisible content, even if in both cases the state is "hidden"!
 
1136
                        duration: visuals.opacity > 0 ? scrollbarThicknessAnimation.duration : 0
 
1137
                        easing: scrollbarThicknessAnimation.easing
 
1138
                    }
 
1139
                    NumberAnimation {
 
1140
                        target: steppersMouseArea
 
1141
                        properties: isVertical ? "height" : "width"
 
1142
                        duration: visuals.opacity > 0 ? scrollbarThicknessAnimation.duration : 0
 
1143
                        easing: scrollbarThicknessAnimation.easing
 
1144
                    }
 
1145
                    ColorAnimation { target: trough; duration: visuals.opacity > 0 ? scrollbarThicknessAnimation.duration : 0 }
 
1146
                }
 
1147
            }
 
1148
        },
 
1149
        Transition {
 
1150
            id: hidingAnimation
 
1151
            from: '*'
 
1152
            to: 'hidden'
 
1153
            SequentialAnimation {
 
1154
                PauseAnimation { id: fadeOutPauseAnim; duration: scrollbarFadeOutPause }
242
1155
                NumberAnimation {
243
1156
                    target: visuals
244
1157
                    property: "opacity"
245
1158
                    duration: scrollbarFadeOutAnimation.duration
246
1159
                    easing: scrollbarFadeOutAnimation.easing
247
1160
                }
 
1161
                PropertyAction {
 
1162
                    target: steppersMouseArea
 
1163
                    properties: "width,height"
 
1164
                }
 
1165
                PropertyAction {
 
1166
                    target: flowContainer
 
1167
                    properties: "thickness,thumbThickness,showSteppers,showTrough,showCornerRect"
 
1168
                }
 
1169
                PropertyAction {
 
1170
                    target: trough
 
1171
                    properties: "color"
 
1172
                }
 
1173
                PropertyAction {
 
1174
                    target: visuals
 
1175
                    properties: "state"
 
1176
                }
248
1177
            }
249
1178
        }
250
1179
    ]
251
 
 
252
 
    function mapToPoint(map)
253
 
    {
254
 
        return Qt.point(map.x, map.y)
255
 
    }
256
 
 
257
 
    SmoothedAnimation {
258
 
        id: scrollAnimation
259
 
 
260
 
        duration: 200
261
 
        easing.type: Easing.InOutQuad
262
 
        target: styledItem.flickableItem
263
 
        property: (isVertical) ? "contentY" : "contentX"
264
 
    }
265
 
 
266
 
    // represents the visible area of the scrollbar where slider and thumb connector are placed
267
 
    Item {
268
 
        id: scrollbarArea
269
 
 
270
 
        property real thickness: scrollAreaThickness
271
 
        property real proximityThickness: (isVertical) ? styledItem.width - thickness : styledItem.height - thickness
272
 
        anchors {
273
 
            fill: parent
274
 
            leftMargin: (!isVertical || frontAligned) ? 0 : proximityThickness
275
 
            rightMargin: (!isVertical || rearAligned) ? 0 : proximityThickness
276
 
            topMargin: (isVertical || topAligned) ? 0 : proximityThickness
277
 
            bottomMargin: (isVertical || bottomAligned) ? 0 : proximityThickness
278
 
        }
279
 
    }
280
 
    // The thumb appears whenever the mouse gets close enough to the scrollbar
281
 
    // and disappears after being for a long enough time far enough of it
282
 
    MouseArea {
283
 
        id: proximityArea
284
 
 
285
 
        anchors {
286
 
            fill: parent
287
 
            leftMargin: (!isVertical)  ? 0 : (frontAligned ? scrollbarArea.thickness : 0)
288
 
            rightMargin: (!isVertical) ? 0 : (rearAligned ? scrollbarArea.thickness : 0)
289
 
            topMargin: (isVertical) ? 0 : (topAligned ? scrollbarArea.thickness : 0)
290
 
            bottomMargin: (isVertical) ? 0 : (bottomAligned ? scrollbarArea.thickness : 0)
291
 
        }
292
 
        propagateComposedEvents: true
293
 
        enabled: isScrollable && interactive
294
 
        hoverEnabled: true
295
 
        onEntered: thumb.show();
296
 
 
297
 
        onPressed: mouse.accepted = false
298
 
        onClicked: mouse.accepted = false
299
 
        onReleased: mouse.accepted = false
300
 
    }
301
 
 
302
 
    // The presence of a mouse enables the interactive thumb
303
 
    // FIXME: Should use form factor hints
304
 
    InverseMouse.onEntered: interactive = true
305
 
 
306
 
    // The slider's position represents which part of the flickable is visible.
307
 
    // The slider's size represents the size the visible part relative to the
308
 
    // total size of the flickable.
309
 
    Item {
310
 
        id: scrollCursor
311
 
        x: (isVertical) ? 0 : scrollbarUtils.sliderPos(styledItem, 0.0, styledItem.width - scrollCursor.width)
312
 
        y: (!isVertical) ? 0 : scrollbarUtils.sliderPos(styledItem, 0.0, styledItem.height - scrollCursor.height)
313
 
        width: (isVertical) ? scrollbarArea.thickness : scrollbarUtils.sliderSize(styledItem, 0.0, flickableItem.width)
314
 
        height: (!isVertical) ? scrollbarArea.thickness : scrollbarUtils.sliderSize(styledItem, 0.0, flickableItem.height)
315
 
 
316
 
        function drag() {
317
 
            scrollbarUtils.dragAndClamp(styledItem, scrollCursor, contentSize, pageSize);
318
 
        }
319
 
    }
320
 
 
321
 
    Rectangle {
322
 
        id: slider
323
 
 
324
 
        color: visuals.sliderColor
325
 
 
326
 
        anchors {
327
 
            left: (isVertical) ? scrollbarArea.left : undefined
328
 
            right: (isVertical) ? scrollbarArea.right : undefined
329
 
            top: (!isVertical) ? scrollbarArea.top : undefined
330
 
            bottom: (!isVertical) ? scrollbarArea.bottom : undefined
331
 
        }
332
 
 
333
 
        x: (isVertical) ? 0 : scrollbarUtils.sliderPos(styledItem, 0.0, styledItem.width - slider.width)
334
 
        y: (!isVertical) ? 0 : scrollbarUtils.sliderPos(styledItem, 0.0, styledItem.height - slider.height)
335
 
        width: (isVertical) ? scrollbarArea.thickness : scrollbarUtils.sliderSize(styledItem, minimumSliderSize, flickableItem.width)
336
 
        height: (!isVertical) ? scrollbarArea.thickness : scrollbarUtils.sliderSize(styledItem, minimumSliderSize, flickableItem.height)
337
 
        radius: visuals.sliderRadius
338
 
 
339
 
        Behavior on width {
340
 
            enabled: (!isVertical)
341
 
            NumberAnimation {
342
 
                duration: visuals.sliderAnimation.duration
343
 
                easing: visuals.sliderAnimation.easing
344
 
            }
345
 
        }
346
 
        Behavior on height {
347
 
            enabled: (isVertical)
348
 
            NumberAnimation {
349
 
                duration: visuals.sliderAnimation.duration
350
 
                easing: visuals.sliderAnimation.easing
351
 
            }
352
 
        }
353
 
 
354
 
        function scroll(amount) {
355
 
            scrollAnimation.to = scrollbarUtils.scrollAndClamp(styledItem, amount, 0.0, contentSize - pageSize);
356
 
            scrollAnimation.restart();
357
 
        }
358
 
    }
359
 
 
360
 
    // The sliderThumbConnector ensures a visual connection between the slider and the thumb
361
 
    Rectangle {
362
 
        id: sliderThumbConnector
363
 
 
364
 
        property real thumbConnectorMargin: visuals.thumbConnectorMargin
365
 
        property bool isThumbAboveSlider: (isVertical) ? thumb.y < slider.y : thumb.x < slider.x
366
 
        anchors {
367
 
            left: (isVertical) ? scrollbarArea.left : (isThumbAboveSlider ? thumb.left : slider.right)
368
 
            right: (isVertical) ? scrollbarArea.right : (isThumbAboveSlider ? slider.left : thumb.right)
369
 
            top: (!isVertical) ? scrollbarArea.top : (isThumbAboveSlider ? thumb.top : slider.bottom)
370
 
            bottom: (!isVertical) ? scrollbarArea.bottom : (isThumbAboveSlider ? slider.top : thumb.bottom)
371
 
 
372
 
            leftMargin : (isVertical) ? 0 : (isThumbAboveSlider ? thumbConnectorMargin : 0)
373
 
            rightMargin : (isVertical) ? 0 : (isThumbAboveSlider ? 0 : thumbConnectorMargin)
374
 
            topMargin : (!isVertical) ? 0 : (isThumbAboveSlider ? thumbConnectorMargin : 0)
375
 
            bottomMargin : (!isVertical) ? 0 : (isThumbAboveSlider ? 0 : thumbConnectorMargin)
376
 
        }
377
 
        color: visuals.thumbConnectorColor
378
 
        opacity: thumb.shown ? 1.0 : 0.0
379
 
        Behavior on opacity {
380
 
            NumberAnimation {
381
 
                duration: visuals.thumbConnectorFading.duration
382
 
                easing: visuals.thumbConnectorFading.easing
383
 
            }
384
 
        }
385
 
    }
386
 
 
387
 
    MouseArea {
388
 
        id: thumbArea
389
 
 
390
 
        property point thumbPoint: mapToPoint(thumb.mapFromItem(thumbArea, mouseX, mouseY))
391
 
        property point thumbTopPoint: mapToPoint(thumbTop.mapFromItem(thumb, thumbPoint.x, thumbPoint.y))
392
 
        property point thumbBottomPoint: mapToPoint(thumbBottom.mapFromItem(thumb, thumbPoint.x, thumbPoint.y))
393
 
        property bool inThumbTop: thumbTop.contains(thumbTopPoint)
394
 
        property bool inThumbBottom: thumbBottom.contains(thumbBottomPoint)
395
 
 
396
 
        anchors {
397
 
            fill: scrollbarArea
398
 
            // set margins adding 2 dp for error area
399
 
            leftMargin: (!isVertical || frontAligned) ? 0 : units.dp(-2) - thumb.width
400
 
            rightMargin: (!isVertical || rearAligned) ? 0 : units.dp(-2) - thumb.width
401
 
            topMargin: (isVertical || topAligned) ?  0 : units.dp(-2) - thumb.height
402
 
            bottomMargin: (isVertical || bottomAligned) ?  0 : units.dp(-2) - thumb.height
403
 
        }
404
 
        enabled: isScrollable && interactive
405
 
        hoverEnabled: true
406
 
        onEntered: thumb.show()
407
 
        onPressed: {
408
 
            if (isVertical) {
409
 
                if (mouseY < thumb.y) {
410
 
                    thumb.placeThumbForeUnderMouse(mouse)
411
 
                } else if (mouseY > (thumb.y + thumb.height)) {
412
 
                    thumb.placeThumbRearUnderMouse(mouse)
413
 
                }
414
 
            } else {
415
 
                if (mouseX < thumb.x) {
416
 
                    thumb.placeThumbForeUnderMouse(mouse)
417
 
                } else if (mouseX > (thumb.x + thumb.width)) {
418
 
                    thumb.placeThumbRearUnderMouse(mouse)
419
 
                }
420
 
            }
421
 
        }
422
 
        onClicked: {
423
 
            if (inThumbBottom)
424
 
                slider.scroll(pageSize)
425
 
            else if (inThumbTop)
426
 
                slider.scroll(-pageSize)
427
 
        }
428
 
 
429
 
        // Dragging behaviour
430
 
        function resetDrag() {
431
 
            thumbYStart = thumb.y
432
 
            thumbXStart = thumb.x
433
 
            dragYStart = drag.target.y
434
 
            dragXStart = drag.target.x
435
 
        }
436
 
 
437
 
        property int thumbYStart
438
 
        property int dragYStart
439
 
        property int dragYAmount: thumbArea.drag.target.y - thumbArea.dragYStart
440
 
        property int thumbXStart
441
 
        property int dragXStart
442
 
        property int dragXAmount: thumbArea.drag.target.x - thumbArea.dragXStart
443
 
        drag {
444
 
            target: scrollCursor
445
 
            axis: (isVertical) ? Drag.YAxis : Drag.XAxis
446
 
            minimumY: 0
447
 
            maximumY: flickableItem.height - scrollCursor.height
448
 
            minimumX: 0
449
 
            maximumX: flickableItem.width - scrollCursor.width
450
 
            onActiveChanged: {
451
 
                if (drag.active) resetDrag()
452
 
            }
453
 
        }
454
 
        // update thumb position
455
 
        onDragYAmountChanged: {
456
 
            if (drag.active) {
457
 
                thumb.y = MathUtils.clamp(thumbArea.thumbYStart + thumbArea.dragYAmount, 0, thumb.maximumPos);
458
 
            }
459
 
        }
460
 
        onDragXAmountChanged: {
461
 
            if (drag.active) {
462
 
                thumb.x = MathUtils.clamp(thumbArea.thumbXStart + thumbArea.dragXAmount, 0, thumb.maximumPos);
463
 
            }
464
 
        }
465
 
 
466
 
        // drag slider and content to the proper position
467
 
        onPositionChanged: {
468
 
            if (pressedButtons == Qt.LeftButton) {
469
 
                scrollCursor.drag()
470
 
            }
471
 
        }
472
 
    }
473
 
 
474
 
    Timer {
475
 
        id: autohideTimer
476
 
 
477
 
        interval: 1000
478
 
        repeat: true
479
 
        onTriggered: if (!proximityArea.containsMouse && !thumbArea.containsMouse && !thumbArea.pressed) thumb.hide()
480
 
    }
481
 
 
482
 
    Item {
483
 
        id: thumb
484
 
        objectName: "interactiveScrollbarThumb"
485
 
 
486
 
        enabled: interactive
487
 
 
488
 
        anchors {
489
 
            left: frontAligned ? slider.left : undefined
490
 
            right: rearAligned ? slider.right : undefined
491
 
            top: topAligned ? slider.top : undefined
492
 
            bottom: bottomAligned ? slider.bottom : undefined
493
 
        }
494
 
 
495
 
        width: childrenRect.width
496
 
        height: childrenRect.height
497
 
 
498
 
        property bool shown
499
 
        property int maximumPos: (isVertical) ? styledItem.height - thumb.height : styledItem.width - thumb.width
500
 
 
501
 
        /* Show the thumb as close as possible to the mouse pointer */
502
 
        onShownChanged: {
503
 
            if (shown) {
504
 
                if (isVertical) {
505
 
                    var mouseY = proximityArea.containsMouse ? proximityArea.mouseY : thumbArea.mouseY;
506
 
                    y = MathUtils.clamp(mouseY - thumb.height / 2, 0, thumb.maximumPos);
507
 
                } else {
508
 
                    var mouseX = proximityArea.containsMouse ? proximityArea.mouseX : thumbArea.mouseX;
509
 
                    x = MathUtils.clamp(mouseX - thumb.width / 2, 0, thumb.maximumPos);
510
 
                }
511
 
            }
512
 
        }
513
 
 
514
 
        function show() {
515
 
            autohideTimer.restart();
516
 
            shown = true;
517
 
        }
518
 
 
519
 
        function hide() {
520
 
            autohideTimer.stop();
521
 
            shown = false;
522
 
        }
523
 
 
524
 
        function placeThumbForeUnderMouse(mouse) {
525
 
            var diff = (isVertical) ? mouse.y - height / 4 : mouse.x - width / 4;
526
 
            positionAnimation.to = MathUtils.clamp(diff, 0, maximumPos);
527
 
            positionAnimation.restart();
528
 
        }
529
 
 
530
 
        function placeThumbRearUnderMouse(mouse) {
531
 
            var diff = (isVertical) ? mouse.y - height * 3 / 4 : mouse.x - width * 3 / 4;
532
 
            positionAnimation.to = MathUtils.clamp(diff, 0, maximumPos);
533
 
            positionAnimation.restart();
534
 
        }
535
 
 
536
 
        NumberAnimation {
537
 
            id: positionAnimation
538
 
 
539
 
            duration: 100
540
 
            easing.type: Easing.InOutQuad
541
 
            target: thumb
542
 
            property: (isVertical) ? "y" : "x"
543
 
        }
544
 
 
545
 
        opacity: shown ? (thumbArea.containsMouse || thumbArea.drag.active ? 1.0 : 0.5) : 0.0
546
 
        Behavior on opacity {
547
 
            NumberAnimation {
548
 
                duration: visuals.thumbFading.duration
549
 
                easing: visuals.thumbFading.easing
550
 
            }
551
 
        }
552
 
 
553
 
        Flow {
554
 
            // disable mirroring as thumbs are placed in the same way no matter of RTL or LTR
555
 
            LayoutMirroring.enabled: false
556
 
            flow: (isVertical) ? Flow.TopToBottom : Flow.LeftToRight
557
 
            Image {
558
 
                id: thumbTop
559
 
                source: thumbArea.inThumbTop && thumbArea.pressed ? visuals.backwardThumbPressed : visuals.backwardThumbReleased
560
 
            }
561
 
            Image {
562
 
                id: thumbBottom
563
 
                source: thumbArea.inThumbBottom && thumbArea.pressed ? visuals.forwardThumbPressed : visuals.forwardThumbReleased
564
 
            }
565
 
        }
566
 
    }
567
1180
}