32
32
- overlay: bool - true if the scrollbar is overlay type
33
33
- overlayOpacityWhenHidden: opacity when hidden
34
34
- overlayOpacityWhenShown: opacity when shown
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
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
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
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.
59
property bool interactive: false
60
property real minimumSliderSize: units.gu(2)
62
property bool overlay: !interactive
63
property real overlayOpacityWhenShown: 0.6
70
property bool initialized: false
72
/*****************************************************
73
* STYLING PROPERTIES *
74
*****************************************************/
75
property bool interactive: isMouseConnected || veryLongContentItem
76
property real minimumSliderSize: units.gu(3)
78
property bool overlay: !alwaysOnScrollbars
79
property real overlayOpacityWhenShown: 1.0
64
80
property real overlayOpacityWhenHidden: 0.0
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"
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)
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 }
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")
81
property real scrollAreaThickness: units.gu(0.5)
82
property real thumbConnectorMargin: units.dp(3)
84
// helper properties to ease code readability
85
property Flickable flickableItem: styledItem.flickableItem
96
property int scrollbarFadeOutPause: 3000
97
property int scrollbarCollapsePause: 1000
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
104
property string hintingStyle: veryLongContentItem ? 'thumb' : 'indicator'
105
onHintingStyleChanged: flashScrollbar()
107
property real thumbsExtremesMargin: units.dp(4)
109
/*****************************************************
110
* HELPER PROPERTIES *
111
*****************************************************/
112
property alias thumb: slider
113
property Item trough: trough
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)
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
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))
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)
166
function scrollToBeginning() {
167
scrollAnimation.to = flickableItem[scrollbarUtils.propOrigin] - visuals.leadingContentMargin
168
scrollAnimation.restart()
170
function scrollToEnd() {
171
scrollAnimation.to = flickableItem[scrollbarUtils.propOrigin]
172
+ totalContentSize - visuals.leadingContentMargin - pageSize
173
scrollAnimation.restart()
175
function resetScrollingToPreDrag() {
176
thumbArea.resetFlickableToPreDragState()
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
187
if (initialized && isScrollable && !draggingThumb && !pressHoldTimer.running
188
&& (state == '' || state === 'hidden' || (__disableStateBinding && visuals.state !== hintingStyle))) {
189
__disableStateBinding = true
191
visuals.state = hintingStyle
192
hintingTimer.restart()
197
opacity: overlayOpacityWhenHidden
198
Component.onCompleted: initialized = true
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
209
//we assume thickness animation duration is enough to complete the
210
//transition to the hinting style
211
interval: scrollbarThicknessAnimation.duration
214
__disableStateBinding = false
99
Object storing property names used in calculations.
220
Object storing property names used in calculations.
102
223
id: scrollbarUtils
104
224
property string propOrigin: (isVertical) ? "originY" : "originX"
105
225
property string propContent: (isVertical) ? "contentY" : "contentX"
106
226
property string propPosRatio: (isVertical) ? "yPosition" : "xPosition"
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.
155
function scrollAndClamp(amount, min, max) {
156
return styledItem.flickableItem[propOrigin] +
157
MathUtils.clamp(styledItem.flickableItem[propContent] - styledItem.flickableItem[propOrigin] + amount,
300
function scrollAndClamp(scrollbar, amount, min, max) {
301
return scrollbar.flickableItem[propOrigin] +
302
MathUtils.clamp(scrollbar.flickableItem[propContent]
303
- scrollbar.flickableItem[propOrigin] + amount,
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.
313
//FIXME: should we change the API and remove pageSize or just pass trough's size as pageSize par?
315
NOTE: when flickable.topMargin is 5GU, contentY has to be -5GU (not 0!) to be at the top of the scrollable!!
168
function dragAndClamp(cursor, contentSize, pageSize) {
169
styledItem.flickableItem[propContent] =
170
styledItem.flickableItem[propOrigin] + cursor[propCoordinate] * contentSize / pageSize;
174
/*****************************************
176
*****************************************/
179
opacity: overlayOpacityWhenHidden
184
if (flickableItem.moving)
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!;
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
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
333
propagateComposedEvents: true
334
preventStealing: false
335
enabled: isScrollable && interactive
336
hoverEnabled: isScrollable && interactive
337
Mouse.ignoreSynthesizedEvents: true
339
isMouseConnected = true
340
proximityArea.containsMouseDevice = true
343
overshootTimer.restart();
344
proximityArea.containsMouseDevice = false
346
onPressed: { mouse.accepted = false }
350
interval: scrollbarThicknessAnimation.duration * 10
354
//each scrollbar connects to both width and height because
355
//we want to show both the scrollbar in both cases
357
target: (flickableItem && initialized) ? flickableItem : null
358
onContentHeightChanged: flashScrollbar()
359
onHeightChanged: flashScrollbar()
360
onContentWidthChanged: flashScrollbar()
361
onWidthChanged: flashScrollbar()
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()
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
388
property real proximityThickness: (isVertical) ? styledItem.width - thickness
389
: styledItem.height - thickness
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
398
flow: (isVertical) ? Flow.TopToBottom : Flow.LeftToRight
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
408
// represents the visible area of the scrollbar where
409
//slider and thumb connector are placed
413
visible: flowContainer.showTrough
418
objectName: "interactiveScrollbarThumb"
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)
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
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)
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
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) {
456
function scroll(amount) {
457
scrollAnimation.to = scrollbarUtils.scrollAndClamp(styledItem, amount, -leadingContentMargin,
458
Math.max(contentSize + trailingContentMargin - pageSize, -leadingContentMargin));
459
scrollAnimation.restart();
462
scrollbarUtils.dragAndClamp(styledItem, slider.relThumbPosition, totalContentSize, pageSize);
466
verticalCenter: (isVertical) ? undefined : trough.verticalCenter
467
horizontalCenter: (isVertical) ? trough.horizontalCenter : undefined
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))
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
482
when: !visuals.draggingThumb
485
value: (isVertical) ? flowContainer.thumbThickness
486
: scrollbarUtils.sliderSize(styledItem, visuals.minimumSliderSize, trough.width - 2 * thumbsExtremesMargin)
489
when: !visuals.draggingThumb
492
value: (!isVertical) ? flowContainer.thumbThickness
493
: scrollbarUtils.sliderSize(styledItem, visuals.minimumSliderSize, trough.height - 2 * thumbsExtremesMargin)
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)
503
property real originXAtDragStart: 0
504
property real originYAtDragStart: 0
505
property real contentXAtDragStart: 0
506
property real contentYAtDragStart: 0
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
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
521
property string scrollingProp: isVertical ? "y" : "x"
522
property string sizeProp: isVertical ? "height" : "width"
524
function initDrag(isMouse) {
525
__disableStateBinding = true
526
thumbArea.saveFlickableScrollingState()
528
slider.mouseDragging = true
529
visuals.state = "steppers"
531
slider.touchDragging = true
532
visuals.state = "thumb"
536
function resetDrag() {
537
if (thumbArea.lockDrag) thumbArea.lockDrag = false
538
slider.mouseDragging = false
539
slider.touchDragging = false
540
__disableStateBinding = false
543
function handlePress(mouseX, mouseY) {
544
if (!thumbArea.pressed) return
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)
554
function saveFlickableScrollingState() {
555
originXAtDragStart = flickableItem.originX
556
originYAtDragStart = flickableItem.originY
557
contentXAtDragStart = flickableItem.contentX
558
contentYAtDragStart = flickableItem.contentY
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) {
572
flickableItem.contentX = contentXAtDragStart
573
flickableItem.contentY = contentYAtDragStart
576
function cacheMousePosition(mouse) {
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)
589
enabled: isScrollable && interactive
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)
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
610
} else if (visuals.veryLongContentItem && slider.containsTouch()) {
613
//propagate otherwise
614
mouse.accepted = false
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)) {
623
cacheMousePosition(mouse)
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
634
//don't clash with scrolling animation
635
if (scrollAnimation.running) scrollAnimation.stop()
637
if (thumbArea.lockDrag) thumbArea.lockDrag = false
644
pressHoldTimer.stop()
648
pressHoldTimer.stop()
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
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
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"!!!
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
689
function startTimer(item) {
694
interval: firstTrigger ? 500 : 250
696
if (firstTrigger) firstTrigger = false
697
if (startedBy !== undefined) {
698
startedBy.handlePress(startedBy.mouseX, startedBy.mouseY)
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.")
713
id: steppersMouseArea
714
//size is handled by the states
716
function handlePress() {
717
var mappedCoordFirstStepper = mapToItem(firstStepper, mouseX, mouseY)
718
var mappedCoordSecondStepper = mapToItem(secondStepper, mouseX, mouseY)
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)
727
enabled: isScrollable && interactive
728
visible: flowContainer.showSteppers
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
736
pressHoldTimer.startTimer(steppersMouseArea)
738
onReleased: pressHoldTimer.stop()
739
onCanceled: pressHoldTimer.stop()
744
//it's left in both cases, otherwise using RTL
745
//would break the layout of the arrow
747
right: isVertical ? parent.right : undefined
749
bottom: !isVertical ? parent.bottom : undefined
752
visible: parent.visible
759
value: visible ? troughThicknessSteppersStyle : 0
765
value: visible ? troughThicknessSteppersStyle : 0
768
anchors.centerIn: parent
769
rotation: isVertical ? 180 : 90
770
source: Qt.resolvedUrl("../artwork/scrollbar_arrow.png")
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
784
visible: parent.visible
787
target: secondStepper
790
value: visible ? troughThicknessSteppersStyle : 0
793
target: secondStepper
796
value: visible ? troughThicknessSteppersStyle : 0
799
anchors.centerIn: parent
800
fillMode: Image.PreserveAspectFit
801
rotation: isVertical ? 0 : -90
802
source: Qt.resolvedUrl("../artwork/scrollbar_arrow.png")
809
//just a hack: a rectangle which covers the corner where scrollbars meet, when they're in steppers style
813
if (styledItem.__buddyScrollbar && styledItem.__buddyScrollbar.__styleInstance
814
&& styledItem.__buddyScrollbar.__styleInstance.isScrollable) {
816
return flowContainer.left
818
if (styledItem.__buddyScrollbar.align === Qt.AlignTrailing) {
819
return flowContainer.right
826
if (styledItem.__buddyScrollbar && styledItem.__buddyScrollbar.__styleInstance
827
&& styledItem.__buddyScrollbar.__styleInstance.isScrollable) {
829
return flowContainer.right
831
if (styledItem.__buddyScrollbar.align === Qt.AlignLeading) {
832
return flowContainer.left
840
if (styledItem.__buddyScrollbar && styledItem.__buddyScrollbar.__styleInstance
841
&& styledItem.__buddyScrollbar.__styleInstance.isScrollable) {
843
if (styledItem.__buddyScrollbar.align === Qt.AlignBottom) {
844
return flowContainer.bottom
854
if (styledItem.__buddyScrollbar && styledItem.__buddyScrollbar.__styleInstance
855
&& styledItem.__buddyScrollbar.__styleInstance.isScrollable) {
857
if (styledItem.__buddyScrollbar.align === Qt.AlignTop) {
858
return flowContainer.top
863
return flowContainer.bottom
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
872
visible: flowContainer.showCornerRect
873
&& styledItem.__buddyScrollbar
874
&& styledItem.__buddyScrollbar.__styleInstance.isScrollable
878
when: !__disableStateBinding
882
if (!isScrollable && !alwaysOnScrollbars)
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) {
891
} else if (thumbStyleFlag || (draggingThumb && slider.touchDragging) || __overshootTimer.running) {
893
} else if (flickableItem.moving || scrollAnimation.running || transitionToIndicator.running) {
895
} else return 'hidden';
196
906
PropertyChanges {
233
duration: scrollbarFadeInAnimation.duration
234
easing: scrollbarFadeInAnimation.easing
240
SequentialAnimation {
241
PauseAnimation { duration: scrollbarFadeOutPause }
1007
from: 'thumb,steppers'
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 }
1015
target: steppersMouseArea
1016
properties: isVertical ? "width" : "height"
1020
target: flowContainer
1021
properties: "thickness,thumbThickness"
1022
duration: scrollbarThicknessAnimation.duration
1023
easing: scrollbarThicknessAnimation.easing
1026
target: steppersMouseArea
1027
properties: isVertical ? "height" : "width"
1028
duration: scrollbarThicknessAnimation.duration
1029
easing: scrollbarThicknessAnimation.easing
1031
ColorAnimation { target: trough; duration: scrollbarThicknessAnimation.duration }
1034
target: flowContainer
1035
properties: "showSteppers,showTrough,showCornerRect"
1040
id: transitionToIndicator
1043
SequentialAnimation {
1048
duration: scrollbarThicknessAnimation.duration
1049
easing: scrollbarThicknessAnimation.easing
1052
target: flowContainer
1053
properties: "thickness,thumbThickness"
1054
duration: scrollbarThicknessAnimation.duration
1055
easing: scrollbarThicknessAnimation.easing
1057
ColorAnimation { target: trough; duration: scrollbarThicknessAnimation.duration }
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.
1065
target: flowContainer
1066
properties: "showSteppers,showTrough,showCornerRect"
1071
id: growingTransition
1073
to: 'thumb,steppers'
1075
SequentialAnimation {
1077
target: flowContainer
1078
properties: "showSteppers,showTrough,showCornerRect"
1081
target: steppersMouseArea
1082
properties: isVertical ? "width" : "height"
1088
duration: scrollbarThicknessAnimation.duration
1089
easing: scrollbarThicknessAnimation.easing
1092
target: flowContainer
1093
properties: "thickness,thumbThickness"
1094
duration: scrollbarThicknessAnimation.duration
1095
easing: scrollbarThicknessAnimation.easing
1098
target: steppersMouseArea
1099
properties: isVertical ? "height" : "width"
1100
duration: scrollbarThicknessAnimation.duration
1101
easing: scrollbarThicknessAnimation.easing
1103
ColorAnimation { target: trough; duration: scrollbarThicknessAnimation.duration }
1108
id: steppersTransition
1110
to: 'thumb,steppers'
1111
SequentialAnimation {
1113
target: flowContainer
1114
properties: "showSteppers,showTrough,showCornerRect"
1117
target: steppersMouseArea
1118
properties: isVertical ? "width" : "height"
1124
duration: scrollbarThicknessAnimation.duration
1125
easing: scrollbarThicknessAnimation.easing
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
1140
target: steppersMouseArea
1141
properties: isVertical ? "height" : "width"
1142
duration: visuals.opacity > 0 ? scrollbarThicknessAnimation.duration : 0
1143
easing: scrollbarThicknessAnimation.easing
1145
ColorAnimation { target: trough; duration: visuals.opacity > 0 ? scrollbarThicknessAnimation.duration : 0 }
1153
SequentialAnimation {
1154
PauseAnimation { id: fadeOutPauseAnim; duration: scrollbarFadeOutPause }
242
1155
NumberAnimation {
244
1157
property: "opacity"
245
1158
duration: scrollbarFadeOutAnimation.duration
246
1159
easing: scrollbarFadeOutAnimation.easing
1162
target: steppersMouseArea
1163
properties: "width,height"
1166
target: flowContainer
1167
properties: "thickness,thumbThickness,showSteppers,showTrough,showCornerRect"
252
function mapToPoint(map)
254
return Qt.point(map.x, map.y)
261
easing.type: Easing.InOutQuad
262
target: styledItem.flickableItem
263
property: (isVertical) ? "contentY" : "contentX"
266
// represents the visible area of the scrollbar where slider and thumb connector are placed
270
property real thickness: scrollAreaThickness
271
property real proximityThickness: (isVertical) ? styledItem.width - thickness : styledItem.height - thickness
274
leftMargin: (!isVertical || frontAligned) ? 0 : proximityThickness
275
rightMargin: (!isVertical || rearAligned) ? 0 : proximityThickness
276
topMargin: (isVertical || topAligned) ? 0 : proximityThickness
277
bottomMargin: (isVertical || bottomAligned) ? 0 : proximityThickness
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
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)
292
propagateComposedEvents: true
293
enabled: isScrollable && interactive
295
onEntered: thumb.show();
297
onPressed: mouse.accepted = false
298
onClicked: mouse.accepted = false
299
onReleased: mouse.accepted = false
302
// The presence of a mouse enables the interactive thumb
303
// FIXME: Should use form factor hints
304
InverseMouse.onEntered: interactive = true
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.
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)
317
scrollbarUtils.dragAndClamp(styledItem, scrollCursor, contentSize, pageSize);
324
color: visuals.sliderColor
327
left: (isVertical) ? scrollbarArea.left : undefined
328
right: (isVertical) ? scrollbarArea.right : undefined
329
top: (!isVertical) ? scrollbarArea.top : undefined
330
bottom: (!isVertical) ? scrollbarArea.bottom : undefined
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
340
enabled: (!isVertical)
342
duration: visuals.sliderAnimation.duration
343
easing: visuals.sliderAnimation.easing
347
enabled: (isVertical)
349
duration: visuals.sliderAnimation.duration
350
easing: visuals.sliderAnimation.easing
354
function scroll(amount) {
355
scrollAnimation.to = scrollbarUtils.scrollAndClamp(styledItem, amount, 0.0, contentSize - pageSize);
356
scrollAnimation.restart();
360
// The sliderThumbConnector ensures a visual connection between the slider and the thumb
362
id: sliderThumbConnector
364
property real thumbConnectorMargin: visuals.thumbConnectorMargin
365
property bool isThumbAboveSlider: (isVertical) ? thumb.y < slider.y : thumb.x < slider.x
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)
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)
377
color: visuals.thumbConnectorColor
378
opacity: thumb.shown ? 1.0 : 0.0
379
Behavior on opacity {
381
duration: visuals.thumbConnectorFading.duration
382
easing: visuals.thumbConnectorFading.easing
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)
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
404
enabled: isScrollable && interactive
406
onEntered: thumb.show()
409
if (mouseY < thumb.y) {
410
thumb.placeThumbForeUnderMouse(mouse)
411
} else if (mouseY > (thumb.y + thumb.height)) {
412
thumb.placeThumbRearUnderMouse(mouse)
415
if (mouseX < thumb.x) {
416
thumb.placeThumbForeUnderMouse(mouse)
417
} else if (mouseX > (thumb.x + thumb.width)) {
418
thumb.placeThumbRearUnderMouse(mouse)
424
slider.scroll(pageSize)
426
slider.scroll(-pageSize)
429
// Dragging behaviour
430
function resetDrag() {
431
thumbYStart = thumb.y
432
thumbXStart = thumb.x
433
dragYStart = drag.target.y
434
dragXStart = drag.target.x
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
445
axis: (isVertical) ? Drag.YAxis : Drag.XAxis
447
maximumY: flickableItem.height - scrollCursor.height
449
maximumX: flickableItem.width - scrollCursor.width
451
if (drag.active) resetDrag()
454
// update thumb position
455
onDragYAmountChanged: {
457
thumb.y = MathUtils.clamp(thumbArea.thumbYStart + thumbArea.dragYAmount, 0, thumb.maximumPos);
460
onDragXAmountChanged: {
462
thumb.x = MathUtils.clamp(thumbArea.thumbXStart + thumbArea.dragXAmount, 0, thumb.maximumPos);
466
// drag slider and content to the proper position
468
if (pressedButtons == Qt.LeftButton) {
479
onTriggered: if (!proximityArea.containsMouse && !thumbArea.containsMouse && !thumbArea.pressed) thumb.hide()
484
objectName: "interactiveScrollbarThumb"
489
left: frontAligned ? slider.left : undefined
490
right: rearAligned ? slider.right : undefined
491
top: topAligned ? slider.top : undefined
492
bottom: bottomAligned ? slider.bottom : undefined
495
width: childrenRect.width
496
height: childrenRect.height
499
property int maximumPos: (isVertical) ? styledItem.height - thumb.height : styledItem.width - thumb.width
501
/* Show the thumb as close as possible to the mouse pointer */
505
var mouseY = proximityArea.containsMouse ? proximityArea.mouseY : thumbArea.mouseY;
506
y = MathUtils.clamp(mouseY - thumb.height / 2, 0, thumb.maximumPos);
508
var mouseX = proximityArea.containsMouse ? proximityArea.mouseX : thumbArea.mouseX;
509
x = MathUtils.clamp(mouseX - thumb.width / 2, 0, thumb.maximumPos);
515
autohideTimer.restart();
520
autohideTimer.stop();
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();
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();
537
id: positionAnimation
540
easing.type: Easing.InOutQuad
542
property: (isVertical) ? "y" : "x"
545
opacity: shown ? (thumbArea.containsMouse || thumbArea.drag.active ? 1.0 : 0.5) : 0.0
546
Behavior on opacity {
548
duration: visuals.thumbFading.duration
549
easing: visuals.thumbFading.easing
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
559
source: thumbArea.inThumbTop && thumbArea.pressed ? visuals.backwardThumbPressed : visuals.backwardThumbReleased
563
source: thumbArea.inThumbBottom && thumbArea.pressed ? visuals.forwardThumbPressed : visuals.forwardThumbReleased