Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qquickscrollbar.cpp
Go to the documentation of this file.
1// Copyright (C) 2017 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
8
9#include <QtQml/qqmlinfo.h>
10#include <QtQuick/private/qquickflickable_p.h>
11#if QT_CONFIG(accessibility)
12#include <QtQuick/private/qquickaccessibleattached_p.h>
13#endif
14
16
17/*!
18 \qmltype ScrollBar
19 \inherits Control
20//! \nativetype QQuickScrollBar
21 \inqmlmodule QtQuick.Controls
22 \since 5.7
23 \ingroup qtquickcontrols-indicators
24 \brief Vertical or horizontal interactive scroll bar.
25
26 \image qtquickcontrols-scrollbar.gif
27
28 ScrollBar is an interactive bar that can be used to scroll to a specific
29 position. A scroll bar can be either \l vertical or \l horizontal, and can
30 be attached to any \l Flickable, such as \l ListView and \l GridView.
31 It can also be used with \l ScrollView.
32
33 \code
34 Flickable {
35 // ...
36 ScrollBar.vertical: ScrollBar { }
37 }
38 \endcode
39
40 \section1 Attaching ScrollBar to a Flickable
41
42 When ScrollBar is attached \l {ScrollBar::vertical}{vertically} or
43 \l {ScrollBar::horizontal}{horizontally} to a Flickable, its geometry and
44 the following properties are automatically set and updated as appropriate:
45
46 \list
47 \li \l orientation
48 \li \l position
49 \li \l {ScrollBar::} {size}
50 \li \l active
51 \endlist
52
53 An attached ScrollBar re-parents itself to the target Flickable. A vertically
54 attached ScrollBar resizes itself to the height of the Flickable, and positions
55 itself to either side of it based on the \l {Control::mirrored}{layout direction}.
56 A horizontally attached ScrollBar resizes itself to the width of the Flickable,
57 and positions itself to the bottom. The automatic geometry management can be disabled
58 by specifying another parent for the attached ScrollBar. This can be useful, for
59 example, if the ScrollBar should be placed outside a clipping Flickable. This is
60 demonstrated by the following example:
61
62 \code
63 Flickable {
64 id: flickable
65 clip: true
66 // ...
67 ScrollBar.vertical: ScrollBar {
68 parent: flickable.parent
69 anchors.top: flickable.top
70 anchors.left: flickable.right
71 anchors.bottom: flickable.bottom
72 }
73 }
74 \endcode
75
76 Notice that ScrollBar does not filter key events of the Flickable it is
77 attached to. The following example illustrates how to implement scrolling
78 with up and down keys:
79
80 \code
81 Flickable {
82 focus: true
83
84 Keys.onUpPressed: scrollBar.decrease()
85 Keys.onDownPressed: scrollBar.increase()
86
87 ScrollBar.vertical: ScrollBar { id: scrollBar }
88 }
89 \endcode
90
91 \section1 Binding the Active State of Horizontal and Vertical Scroll Bars
92
93 Horizontal and vertical scroll bars do not share the \l active state with
94 each other by default. In order to keep both bars visible whilst scrolling
95 to either direction, establish a two-way binding between the active states
96 as presented by the following example:
97
98 \snippet qtquickcontrols-scrollbar-active.qml 1
99
100 \section1 Non-attached Scroll Bars
101
102 It is possible to create an instance of ScrollBar without using the
103 attached property API. This is useful when the behavior of the attached
104 scroll bar is not sufficient or a \l Flickable is not in use. In the
105 following example, horizontal and vertical scroll bars are used to
106 scroll over the text without using \l Flickable:
107
108 \snippet qtquickcontrols-scrollbar-non-attached.qml 1
109
110 \image qtquickcontrols-scrollbar-non-attached.png
111
112 When using a non-attached ScrollBar, the following must be done manually:
113
114 \list
115 \li Layout the scroll bar (with the \l {Item::}{x} and \l {Item::}{y} or
116 \l {Item::}{anchors} property, for example).
117 \li Set the \l size and \l position properties to determine the size and position
118 of the scroll bar in relation to the scrolled item.
119 \li Set the \l active property to determine when the scroll bar will be
120 visible.
121 \endlist
122
123 \include varying-delegate-heights-section.qdocinc {file} {1} {ScrollBar}
124
125 \sa ScrollIndicator, ScrollView, {Customizing ScrollBar}, {Indicator Controls}
126*/
127
128static const QQuickItemPrivate::ChangeTypes QsbChangeTypes = QQuickItemPrivate::Geometry | QQuickItemPrivate::Destroyed;
129static const QQuickItemPrivate::ChangeTypes QsbHorizontalChangeTypes = QsbChangeTypes | QQuickItemPrivate::ImplicitHeight;
130static const QQuickItemPrivate::ChangeTypes QsbVerticalChangeTypes = QsbChangeTypes | QQuickItemPrivate::ImplicitWidth;
131
133{
134 qreal visualPos = position;
135
136 if (minimumSize > size && size != 1.0)
137 visualPos = position / (1.0 - size) * (1.0 - minimumSize);
138
139 qreal maximumSize = qMax<qreal>(0.0, 1.0 - visualPos);
140 qreal visualSize = qMax<qreal>(minimumSize,
141 qMin<qreal>(qMax(size, minimumSize) + qMin<qreal>(0, visualPos),
142 maximumSize));
143
144 visualPos = qMax<qreal>(0,qMin<qreal>(visualPos,qMax<qreal>(0, 1.0 - visualSize)));
145
146 return VisualArea(visualPos, visualSize);
147}
148
150{
151 if (minimumSize > size && minimumSize != 1.0)
152 return position * (1.0 - size) / (1.0 - minimumSize);
153 return position;
154}
155
157{
158 const qreal effectiveStep = stepSize * (1.0 - size);
159 if (qFuzzyIsNull(effectiveStep))
160 return position;
161
162 return qRound(position / effectiveStep) * effectiveStep;
163}
164
165qreal QQuickScrollBarPrivate::positionAt(const QPointF &point) const
166{
167 Q_Q(const QQuickScrollBar);
168 if (orientation == Qt::Horizontal)
169 return logicalPosition(point.x() - q->leftPadding()) / q->availableWidth();
170 else
171 return logicalPosition(point.y() - q->topPadding()) / q->availableHeight();
172}
173
175{
176 Q_Q(QQuickScrollBar);
177 if (interactive == enabled)
178 return;
179
180 interactive = enabled;
181 if (interactive) {
182 q->setAcceptedMouseButtons(Qt::LeftButton);
183#if QT_CONFIG(quicktemplates2_multitouch)
184 q->setAcceptTouchEvents(true);
185#endif
186#if QT_CONFIG(cursor)
187 q->setCursor(Qt::ArrowCursor);
188#endif
189 } else {
190 q->setAcceptedMouseButtons(Qt::NoButton);
191#if QT_CONFIG(quicktemplates2_multitouch)
192 q->setAcceptTouchEvents(false);
193#endif
194#if QT_CONFIG(cursor)
195 q->unsetCursor();
196#endif
197 q->ungrabMouse();
198 }
199 emit q->interactiveChanged();
200}
201
203{
204 Q_Q(QQuickScrollBar);
205#if QT_CONFIG(quicktemplates2_hover)
206 bool hover = hovered;
207#else
208 bool hover = false;
209#endif
210 q->setActive(moving || (interactive && (pressed || hover)));
211}
212
214{
215 Q_Q(QQuickScrollBar);
216 if (!contentItem)
217 return;
218
219 // - negative overshoot (pos < 0): clamp the pos to 0, and deduct the overshoot from the size
220 // - positive overshoot (pos + size > 1): clamp the size to 1-pos
221 const VisualArea visual = visualArea();
222
223 if (orientation == Qt::Horizontal) {
224 contentItem->setPosition(QPointF(q->leftPadding() + visual.position * q->availableWidth(), q->topPadding()));
225 contentItem->setSize(QSizeF(q->availableWidth() * visual.size, q->availableHeight()));
226 } else {
227 contentItem->setPosition(QPointF(q->leftPadding(), q->topPadding() + visual.position * q->availableHeight()));
228 contentItem->setSize(QSizeF(q->availableWidth(), q->availableHeight() * visual.size));
229 }
230}
231
233{
234 Q_Q(QQuickScrollBar);
235 QQuickControlPrivate::itemImplicitWidthChanged(item);
236 QQuickIndicatorButton *indicatorButton = q->decreaseVisual();
237 if (!indicatorButton || item != indicatorButton->indicator()) {
238 indicatorButton = q->increaseVisual();
239 if (!indicatorButton || item != indicatorButton->indicator())
240 return;
241 }
242 if (indicatorButton)
243 emit indicatorButton->implicitIndicatorWidthChanged();
244}
245
247{
248 Q_Q(QQuickScrollBar);
249 QQuickControlPrivate::itemImplicitHeightChanged(item);
250 QQuickIndicatorButton *indicatorButton = q->decreaseVisual();
251 if (!indicatorButton || item != indicatorButton->indicator()) {
252 indicatorButton = q->increaseVisual();
253 if (!indicatorButton || item != indicatorButton->indicator())
254 return;
255 }
256 if (indicatorButton)
257 emit indicatorButton->implicitIndicatorHeightChanged();
258}
259
260bool QQuickScrollBarPrivate::handlePress(const QPointF &point, ulong timestamp)
261{
262 Q_Q(QQuickScrollBar);
263 QQuickControlPrivate::handlePress(point, timestamp);
264 if (QQuickIndicatorButton *indicatorButton = q->decreaseVisual()) {
265 QQuickItem *decreaseArrow = indicatorButton->indicator();
266 if (decreaseArrow && decreaseArrow->contains(q->mapToItem(decreaseArrow, point + QPointF(0.5, 0.5)))) {
267 indicatorButton->setPressed(true);
268 q->decrease();
269 return true;
270 }
271 }
272
273 if (QQuickIndicatorButton *increaseObject = q->increaseVisual()) {
274 QQuickItem *increaseArrow = increaseObject->indicator();
275 if (increaseArrow && increaseArrow->contains(q->mapToItem(increaseArrow, point + QPointF(0.5, 0.5)))) {
276 increaseObject->setPressed(true);
277 q->increase();
278 return true;
279 }
280 }
281
282 offset = positionAt(point) - position;
283 qreal sz = qMax(size, logicalPosition(minimumSize));
284 if (offset < 0 || offset > sz)
285 offset = sz / 2;
286 q->setPressed(true);
287 return true;
288}
289
290bool QQuickScrollBarPrivate::handleMove(const QPointF &point, ulong timestamp)
291{
292 Q_Q(QQuickScrollBar);
293 QQuickControlPrivate::handleMove(point, timestamp);
294
295 /*
296 * handleMove() will be called as soon as you hold the mouse button down *anywhere* on the
297 * ScrollBar, including the increase/decrease button indicator areas. So without the following
298 * early return, it would move the scrollbar handle to one of its extremeties. That would
299 * ruin the behavior we would like when clicking e.g. the "increase button": To step the
300 * scrollbar gently.
301 */
302 if (!pressed)
303 return true;
304
305 qreal pos = qMax<qreal>(0.0, qMin<qreal>(positionAt(point) - offset, 1.0 - size));
306 if (snapMode == QQuickScrollBar::SnapAlways)
307 pos = snapPosition(pos);
308 q->setPosition(pos);
309 return true;
310}
311
312bool QQuickScrollBarPrivate::handleRelease(const QPointF &point, ulong timestamp)
313{
314 Q_Q(QQuickScrollBar);
315 QQuickControlPrivate::handleRelease(point, timestamp);
316
317 if (orientation == Qt::Vertical) {
318 if (point.y() < q->topPadding() || point.y() >= (q->height() - q->bottomPadding()))
319 return true;
320 } else /* orientation == Qt::Horizontal */{
321 if (point.x() < q->leftPadding() || point.x() >= (q->width() - q->rightPadding()))
322 return true;
323 }
324
325 qreal pos = qMax<qreal>(0.0, qMin<qreal>(positionAt(point) - offset, 1.0 - size));
326 if (snapMode != QQuickScrollBar::NoSnap)
327 pos = snapPosition(pos);
328 q->setPosition(pos);
329 offset = 0.0;
330 q->setPressed(false);
331 return true;
332}
333
335{
336 Q_Q(QQuickScrollBar);
337 QQuickControlPrivate::handleUngrab();
338 offset = 0.0;
339 q->setPressed(false);
340}
341
342void QQuickScrollBarPrivate::visualAreaChange(const VisualArea &newVisualArea, const VisualArea &oldVisualArea)
343{
344 Q_Q(QQuickScrollBar);
345 if (!qFuzzyCompare(newVisualArea.size, oldVisualArea.size))
346 emit q->visualSizeChanged();
347 if (!qFuzzyCompare(newVisualArea.position, oldVisualArea.position))
348 emit q->visualPositionChanged();
349}
350
351void QQuickScrollBarPrivate::updateHover(const QPointF &pos, std::optional<bool> newHoverState)
352{
353 Q_Q(QQuickScrollBar);
354 auto updateHoverOnButton = [&](QQuickIndicatorButton *sbButton) {
355 if (sbButton) {
356 bool hovered = newHoverState.value_or(false);
357 if (!newHoverState.has_value()) {
358 if (QQuickItem *indicator = sbButton->indicator())
359 hovered = indicator->contains(q->mapToItem(indicator, pos));
360 }
361 sbButton->setHovered(hovered);
362 }
363 };
364 updateHoverOnButton(q->decreaseVisual());
365 updateHoverOnButton(q->increaseVisual());
366}
367
368QQuickScrollBar::QQuickScrollBar(QQuickItem *parent)
369 : QQuickControl(*(new QQuickScrollBarPrivate), parent)
370{
371 Q_D(QQuickScrollBar);
372 d->decreaseVisual = new QQuickIndicatorButton(this);
373 d->increaseVisual = new QQuickIndicatorButton(this);
374 d->setSizePolicy(QLayoutPolicy::Preferred, QLayoutPolicy::Fixed);
375 setKeepMouseGrab(true);
376 setAcceptedMouseButtons(Qt::LeftButton);
377#if QT_CONFIG(quicktemplates2_multitouch)
378 setAcceptTouchEvents(true);
379#endif
380#if QT_CONFIG(cursor)
381 setCursor(Qt::ArrowCursor);
382#endif
383}
384
385QQuickScrollBarAttached *QQuickScrollBar::qmlAttachedProperties(QObject *object)
386{
387 return new QQuickScrollBarAttached(object);
388}
389
390/*!
391 \qmlproperty real QtQuick.Controls::ScrollBar::size
392
393 This property holds the size of the scroll bar, scaled to \c {0.0 - 1.0}.
394
395 \sa {Flickable::visibleArea.heightRatio}{Flickable::visibleArea}
396
397 This property is automatically set when the scroll bar is
398 \l {Attaching ScrollBar to a Flickable}{attached to a flickable}.
399
400 \sa minimumSize, visualSize
401*/
402qreal QQuickScrollBar::size() const
403{
404 Q_D(const QQuickScrollBar);
405 return d->size;
406}
407
408void QQuickScrollBar::setSize(qreal size)
409{
410 Q_D(QQuickScrollBar);
411 if (!qt_is_finite(size))
412 return;
413 size = qBound(0.0, size, 1.0);
414 if (qFuzzyCompare(d->size, size))
415 return;
416
417 const auto oldVisualArea = d->visualArea();
418 d->size = size;
419 if (d->size + d->position > 1.0) {
420 d->setPosition(1.0 - d->size, false);
421 }
422
423 if (isComponentComplete())
424 d->resizeContent();
425 emit sizeChanged();
426 d->visualAreaChange(d->visualArea(), oldVisualArea);
427}
428
429/*!
430 \qmlproperty real QtQuick.Controls::ScrollBar::position
431
432 This property holds the position of the scroll bar, scaled to \c {0.0 - 1.0}.
433
434 The largest valid scrollbar position is \c {(1.0 - size)}. This gives
435 correct behavior for the most used case where moving the scrollbar
436 to the end will put the end of the document at the lower end of the
437 visible area of the connected Flickable.
438
439 \sa {Flickable::visibleArea.yPosition}{Flickable::visibleArea}
440
441 This property is automatically set when the scroll bar is
442 \l {Attaching ScrollBar to a Flickable}{attached to a flickable}.
443
444 \sa visualPosition
445*/
446qreal QQuickScrollBar::position() const
447{
448 Q_D(const QQuickScrollBar);
449 return d->position;
450}
451
452void QQuickScrollBar::setPosition(qreal position)
453{
454 Q_D(QQuickScrollBar);
455 d->setPosition(position);
456}
457
458void QQuickScrollBarPrivate::setPosition(qreal newPosition, bool notifyVisualChange)
459{
460 Q_Q(QQuickScrollBar);
461 if (!qt_is_finite(newPosition) || qFuzzyCompare(position, newPosition))
462 return;
463
464 auto oldVisualArea = visualArea();
465 position = newPosition;
466 if (q->isComponentComplete())
468 emit q->positionChanged();
469 if (notifyVisualChange)
470 visualAreaChange(visualArea(), oldVisualArea);
471}
472
473/*!
474 \qmlproperty real QtQuick.Controls::ScrollBar::stepSize
475
476 This property holds the step size. The default value is \c 0.0.
477
478 \sa snapMode, increase(), decrease()
479*/
480qreal QQuickScrollBar::stepSize() const
481{
482 Q_D(const QQuickScrollBar);
483 return d->stepSize;
484}
485
486void QQuickScrollBar::setStepSize(qreal step)
487{
488 Q_D(QQuickScrollBar);
489 if (!qt_is_finite(step) || qFuzzyCompare(d->stepSize, step))
490 return;
491
492 d->stepSize = step;
493 emit stepSizeChanged();
494}
495
496/*!
497 \qmlproperty bool QtQuick.Controls::ScrollBar::active
498
499 This property holds whether the scroll bar is active, i.e. when it's \l pressed
500 or the attached Flickable is \l {Flickable::moving}{moving}.
501
502 It is possible to keep \l {Binding the Active State of Horizontal and Vertical Scroll Bars}
503 {both horizontal and vertical bars visible} while scrolling in either direction.
504
505 This property is automatically set when the scroll bar is
506 \l {Attaching ScrollBar to a Flickable}{attached to a flickable}.
507*/
508bool QQuickScrollBar::isActive() const
509{
510 Q_D(const QQuickScrollBar);
511 return d->active;
512}
513
514void QQuickScrollBar::setActive(bool active)
515{
516 Q_D(QQuickScrollBar);
517 if (d->active == active)
518 return;
519
520 d->active = active;
521 emit activeChanged();
522}
523
524/*!
525 \qmlproperty bool QtQuick.Controls::ScrollBar::pressed
526
527 This property holds whether the scroll bar is pressed.
528*/
529bool QQuickScrollBar::isPressed() const
530{
531 Q_D(const QQuickScrollBar);
532 return d->pressed;
533}
534
535void QQuickScrollBar::setPressed(bool pressed)
536{
537 Q_D(QQuickScrollBar);
538 if (!pressed) {
539 if (QQuickIndicatorButton *button = decreaseVisual())
540 button->setPressed(false);
541 if (QQuickIndicatorButton *button = increaseVisual())
542 button->setPressed(false);
543 }
544 if (d->pressed == pressed)
545 return;
546
547 d->pressed = pressed;
548 setAccessibleProperty("pressed", pressed);
549 d->updateActive();
550 emit pressedChanged();
551}
552
553/*!
554 \qmlproperty enumeration QtQuick.Controls::ScrollBar::orientation
555
556 This property holds the orientation of the scroll bar.
557
558 Possible values:
559 \value Qt.Horizontal Horizontal
560 \value Qt.Vertical Vertical (default)
561
562 This property is automatically set when the scroll bar is
563 \l {Attaching ScrollBar to a Flickable}{attached to a flickable}.
564
565 \sa horizontal, vertical
566*/
567Qt::Orientation QQuickScrollBar::orientation() const
568{
569 Q_D(const QQuickScrollBar);
570 return d->orientation;
571}
572
573void QQuickScrollBar::setOrientation(Qt::Orientation orientation)
574{
575 Q_D(QQuickScrollBar);
576 if (d->orientation == orientation)
577 return;
578
579 if (orientation == Qt::Horizontal)
580 d->setSizePolicy(QLayoutPolicy::Preferred, QLayoutPolicy::Fixed);
581 else
582 d->setSizePolicy(QLayoutPolicy::Fixed, QLayoutPolicy::Preferred);
583
584 d->orientation = orientation;
585 if (isComponentComplete())
586 d->resizeContent();
587 emit orientationChanged();
588}
589
590/*!
591 \since QtQuick.Controls 2.2 (Qt 5.9)
592 \qmlproperty enumeration QtQuick.Controls::ScrollBar::snapMode
593
594 This property holds the snap mode.
595
596 Possible values:
597 \value ScrollBar.NoSnap The scrollbar does not snap (default).
598 \value ScrollBar.SnapAlways The scrollbar snaps while dragged.
599 \value ScrollBar.SnapOnRelease The scrollbar does not snap while being dragged, but only after released.
600
601 In the following table, the various modes are illustrated with animations.
602 The movement and the \l stepSize (\c 0.25) are identical in each animation.
603
604 \table
605 \header
606 \row \li \b Value \li \b Example
607 \row \li \c ScrollBar.NoSnap \li \image qtquickcontrols-scrollbar-nosnap.gif
608 \row \li \c ScrollBar.SnapAlways \li \image qtquickcontrols-scrollbar-snapalways.gif
609 \row \li \c ScrollBar.SnapOnRelease \li \image qtquickcontrols-scrollbar-snaponrelease.gif
610 \endtable
611
612 \sa stepSize
613*/
614QQuickScrollBar::SnapMode QQuickScrollBar::snapMode() const
615{
616 Q_D(const QQuickScrollBar);
617 return d->snapMode;
618}
619
620void QQuickScrollBar::setSnapMode(SnapMode mode)
621{
622 Q_D(QQuickScrollBar);
623 if (d->snapMode == mode)
624 return;
625
626 d->snapMode = mode;
627 emit snapModeChanged();
628}
629
630/*!
631 \since QtQuick.Controls 2.2 (Qt 5.9)
632 \qmlproperty bool QtQuick.Controls::ScrollBar::interactive
633
634 This property holds whether the scroll bar is interactive. The default value is \c true.
635
636 A non-interactive scroll bar is visually and behaviorally similar to \l ScrollIndicator.
637 This property is useful for switching between typical mouse- and touch-orientated UIs
638 with interactive and non-interactive scroll bars, respectively.
639*/
640bool QQuickScrollBar::isInteractive() const
641{
642 Q_D(const QQuickScrollBar);
643 return d->interactive;
644}
645
646void QQuickScrollBar::setInteractive(bool interactive)
647{
648 Q_D(QQuickScrollBar);
649 d->explicitInteractive = true;
650 d->setInteractive(interactive);
651}
652
653void QQuickScrollBar::resetInteractive()
654{
655 Q_D(QQuickScrollBar);
656 d->explicitInteractive = false;
657 d->setInteractive(true);
658}
659
660/*!
661 \since QtQuick.Controls 2.2 (Qt 5.9)
662 \qmlproperty enumeration QtQuick.Controls::ScrollBar::policy
663
664 This property holds the policy of the scroll bar. The default policy is \c ScrollBar.AsNeeded.
665
666 Possible values:
667 \value ScrollBar.AsNeeded The scroll bar is only shown when the content is too large to fit.
668 \value ScrollBar.AlwaysOff The scroll bar is never shown.
669 \value ScrollBar.AlwaysOn The scroll bar is always shown.
670
671 The following example keeps the vertical scroll bar always visible:
672
673 \snippet qtquickcontrols-scrollbar-policy-alwayson.qml 1
674
675 Styles may use this property in combination with the \l active property
676 in order to implement transient scroll bars. Transient scroll bars are
677 hidden shortly after the last interaction event (hover or press). This
678 is typically done by animating the opacity of the scroll bar. To override
679 this behavior, set the policy to \c ScrollBar.AlwaysOn or
680 \c ScrollBar.AlwaysOff, depending on the size of the content compared to
681 its view. For example, for a vertical \l ListView:
682
683 \snippet qtquickcontrols-scrollbar-policy-alwayson-when-needed.qml 1
684*/
685QQuickScrollBar::Policy QQuickScrollBar::policy() const
686{
687 Q_D(const QQuickScrollBar);
688 return d->policy;
689}
690
691void QQuickScrollBar::setPolicy(Policy policy)
692{
693 Q_D(QQuickScrollBar);
694 if (d->policy == policy)
695 return;
696
697 d->policy = policy;
698 emit policyChanged();
699}
700
701/*!
702 \since QtQuick.Controls 2.3 (Qt 5.10)
703 \qmlproperty bool QtQuick.Controls::ScrollBar::horizontal
704 \readonly
705
706 This property holds whether the scroll bar is horizontal.
707
708 \sa orientation
709*/
710bool QQuickScrollBar::isHorizontal() const
711{
712 Q_D(const QQuickScrollBar);
713 return d->orientation == Qt::Horizontal;
714}
715
716/*!
717 \since QtQuick.Controls 2.3 (Qt 5.10)
718 \qmlproperty bool QtQuick.Controls::ScrollBar::vertical
719 \readonly
720
721 This property holds whether the scroll bar is vertical.
722
723 \sa orientation
724*/
725bool QQuickScrollBar::isVertical() const
726{
727 Q_D(const QQuickScrollBar);
728 return d->orientation == Qt::Vertical;
729}
730
731/*!
732 \since QtQuick.Controls 2.4 (Qt 5.11)
733 \qmlproperty real QtQuick.Controls::ScrollBar::minimumSize
734
735 This property holds the minimum size of the scroll bar, scaled to \c {0.0 - 1.0}.
736
737 \sa size, visualSize, visualPosition
738*/
739qreal QQuickScrollBar::minimumSize() const
740{
741 Q_D(const QQuickScrollBar);
742 return d->minimumSize;
743}
744
745void QQuickScrollBar::setMinimumSize(qreal minimumSize)
746{
747 Q_D(QQuickScrollBar);
748 if (!qt_is_finite(minimumSize) || qFuzzyCompare(d->minimumSize, minimumSize))
749 return;
750
751 auto oldVisualArea = d->visualArea();
752 d->minimumSize = qBound(0.0, minimumSize, 1.0);
753 if (isComponentComplete())
754 d->resizeContent();
755 emit minimumSizeChanged();
756 d->visualAreaChange(d->visualArea(), oldVisualArea);
757}
758
759/*!
760 \since QtQuick.Controls 2.4 (Qt 5.11)
761 \qmlproperty real QtQuick.Controls::ScrollBar::visualSize
762 \readonly
763
764 This property holds the effective visual size of the scroll bar,
765 which may be limited by the \l {minimumSize}{minimum size}.
766
767 \sa size, minimumSize
768*/
769qreal QQuickScrollBar::visualSize() const
770{
771 Q_D(const QQuickScrollBar);
772 return d->visualArea().size;
773}
774
775/*!
776 \since QtQuick.Controls 2.4 (Qt 5.11)
777 \qmlproperty real QtQuick.Controls::ScrollBar::visualPosition
778 \readonly
779
780 This property holds the effective visual position of the scroll bar,
781 which may be limited by the \l {minimumSize}{minimum size}.
782
783 \sa position, minimumSize
784*/
785qreal QQuickScrollBar::visualPosition() const
786{
787 Q_D(const QQuickScrollBar);
788 return d->visualArea().position;
789}
790
791QQuickIndicatorButton *QQuickScrollBar::decreaseVisual()
792{
793 Q_D(QQuickScrollBar);
794 return d->decreaseVisual;
795}
796
797QQuickIndicatorButton *QQuickScrollBar::increaseVisual()
798{
799 Q_D(QQuickScrollBar);
800 return d->increaseVisual;
801}
802
803/*!
804 \qmlmethod void QtQuick.Controls::ScrollBar::increase()
805
806 Increases the position by \l stepSize or \c 0.1 if stepSize is \c 0.0.
807
808 \sa stepSize
809*/
810void QQuickScrollBar::increase()
811{
812 Q_D(QQuickScrollBar);
813 qreal step = qFuzzyIsNull(d->stepSize) ? 0.1 : d->stepSize;
814 bool wasActive = d->active;
815 setActive(true);
816 setPosition(qMin<qreal>(1.0 - d->size, d->position + step));
817 setActive(wasActive);
818}
819
820/*!
821 \qmlmethod void QtQuick.Controls::ScrollBar::decrease()
822
823 Decreases the position by \l stepSize or \c 0.1 if stepSize is \c 0.0.
824
825 \sa stepSize
826*/
827void QQuickScrollBar::decrease()
828{
829 Q_D(QQuickScrollBar);
830 qreal step = qFuzzyIsNull(d->stepSize) ? 0.1 : d->stepSize;
831 bool wasActive = d->active;
832 setActive(true);
833 setPosition(qMax<qreal>(0.0, d->position - step));
834 setActive(wasActive);
835}
836
837void QQuickScrollBar::mousePressEvent(QMouseEvent *event)
838{
839 Q_D(QQuickScrollBar);
840 QQuickControl::mousePressEvent(event);
841 d->handleMove(event->position(), event->timestamp());
842}
843
844#if QT_CONFIG(quicktemplates2_hover)
845void QQuickScrollBar::hoverChange()
846{
847 Q_D(QQuickScrollBar);
848 d->updateActive();
849}
850
851void QQuickScrollBar::hoverEnterEvent(QHoverEvent *event)
852{
853 Q_D(QQuickScrollBar);
854 QQuickControl::hoverEnterEvent(event);
855 d->updateHover(event->position());
856 event->ignore();
857}
858
859void QQuickScrollBar::hoverMoveEvent(QHoverEvent *event)
860{
861 Q_D(QQuickScrollBar);
862 QQuickControl::hoverMoveEvent(event);
863 d->updateHover(event->position());
864 event->ignore();
865}
866
867void QQuickScrollBar::hoverLeaveEvent(QHoverEvent *event)
868{
869 Q_D(QQuickScrollBar);
870 QQuickControl::hoverLeaveEvent(event);
871
872 d->updateHover(QPoint(), false); //position is not needed when we force it to unhover
873 event->ignore();
874}
875#endif
876
877void QQuickScrollBar::classBegin()
878{
879 Q_D(QQuickScrollBar);
880 QQuickControl::classBegin();
881
882 QQmlContext *context = qmlContext(this);
883 if (context) {
884 QQmlEngine::setContextForObject(d->decreaseVisual, context);
885 QQmlEngine::setContextForObject(d->increaseVisual, context);
886 }
887}
888
889void QQuickScrollBar::componentComplete()
890{
891 Q_D(QQuickScrollBar);
892 QQuickIndicatorButtonPrivate::get(d->decreaseVisual)->executeIndicator(true);
893 QQuickIndicatorButtonPrivate::get(d->increaseVisual)->executeIndicator(true);
894
895 QQuickControl::componentComplete();
896}
897
898#if QT_CONFIG(accessibility)
899void QQuickScrollBar::accessibilityActiveChanged(bool active)
900{
901 QQuickControl::accessibilityActiveChanged(active);
902
903 Q_D(QQuickScrollBar);
904 if (active) {
905 setAccessibleProperty("pressed", d->pressed);
906
907 if (QQuickAccessibleAttached *accessibleAttached = QQuickControlPrivate::accessibleAttached(this)) {
908 connect(accessibleAttached, &QQuickAccessibleAttached::increaseAction, this, &QQuickScrollBar::increase);
909 connect(accessibleAttached, &QQuickAccessibleAttached::decreaseAction, this, &QQuickScrollBar::decrease);
910 }
911 } else {
912 if (QQuickAccessibleAttached *accessibleAttached = QQuickControlPrivate::accessibleAttached(this)) {
913 disconnect(accessibleAttached, &QQuickAccessibleAttached::increaseAction, this, &QQuickScrollBar::increase);
914 disconnect(accessibleAttached, &QQuickAccessibleAttached::decreaseAction, this, &QQuickScrollBar::decrease);
915 }
916 }
917}
918
919QAccessible::Role QQuickScrollBar::accessibleRole() const
920{
921 return QAccessible::ScrollBar;
922}
923#endif
924
926{
927 if (flickable) {
928 // NOTE: Use removeItemChangeListener(Geometry) instead of updateOrRemoveGeometryChangeListener(Size).
929 // The latter doesn't remove the listener but only resets its types. Thus, it leaves behind a dangling
930 // pointer on destruction.
931 QQuickItemPrivate::get(flickable)->removeItemChangeListener(this, QQuickItemPrivate::Geometry);
932 QQuickItemPrivate::get(flickable)->removeItemChangeListener(this, QQuickItemPrivate::Destroyed);
933 if (horizontal)
935 if (vertical)
937 }
938
939 flickable = item;
940
941 if (item) {
942 // Don't know how to combine these calls into one, and as long as they're separate calls,
943 // the remove* calls above need to be separate too, otherwise they will have no effect.
944 QQuickItemPrivate::get(item)->updateOrAddGeometryChangeListener(this, QQuickGeometryChange::Size);
945 QQuickItemPrivate::get(item)->updateOrAddItemChangeListener(this, QQuickItemPrivate::Destroyed);
946 if (horizontal)
948 if (vertical)
950 }
951}
952
954{
955 Q_ASSERT(flickable && horizontal);
956
957 connect(flickable, &QQuickFlickable::movingHorizontallyChanged, this, &QQuickScrollBarAttachedPrivate::activateHorizontal);
958
959 // TODO: export QQuickFlickableVisibleArea
960 QObject *area = flickable->property("visibleArea").value<QObject *>();
961 QObject::connect(area, SIGNAL(widthRatioChanged(qreal)), horizontal, SLOT(setSize(qreal)));
962 QObject::connect(area, SIGNAL(xPositionChanged(qreal)), horizontal, SLOT(setPosition(qreal)));
963
964 // ensure that the ScrollBar is stacked above the Flickable in a ScrollView
965 QQuickItem *parent = horizontal->parentItem();
966 if (parent && parent == flickable->parentItem())
967 horizontal->stackAfter(flickable);
968
969 // If a scroll bar was previously hidden (due to e.g. setting a new contentItem
970 // on a ScrollView), we need to make sure that we un-hide it.
971 if (auto control = qobject_cast<QQuickControl*>(q_func()->parent())) {
972 const auto visibility = horizontal->policy() != QQuickScrollBar::AlwaysOff
973 ? QQuickControlPrivate::UnhideVisibility::Show : QQuickControlPrivate::UnhideVisibility::Hide;
974 QQuickControlPrivate::unhideOldItem(control, horizontal, visibility);
975 }
976
978 horizontal->setSize(area->property("widthRatio").toReal());
979 horizontal->setPosition(area->property("xPosition").toReal());
980}
981
983{
984 Q_ASSERT(flickable && vertical);
985
986 connect(flickable, &QQuickFlickable::movingVerticallyChanged, this, &QQuickScrollBarAttachedPrivate::activateVertical);
987
988 // TODO: export QQuickFlickableVisibleArea
989 QObject *area = flickable->property("visibleArea").value<QObject *>();
990 QObject::connect(area, SIGNAL(heightRatioChanged(qreal)), vertical, SLOT(setSize(qreal)));
991 QObject::connect(area, SIGNAL(yPositionChanged(qreal)), vertical, SLOT(setPosition(qreal)));
992
993 // ensure that the ScrollBar is stacked above the Flickable in a ScrollView
994 QQuickItem *parent = vertical->parentItem();
995 if (parent && parent == flickable->parentItem())
996 vertical->stackAfter(flickable);
997
998 if (auto control = qobject_cast<QQuickControl*>(q_func()->parent())) {
999 const auto visibility = vertical->policy() != QQuickScrollBar::AlwaysOff
1000 ? QQuickControlPrivate::UnhideVisibility::Show : QQuickControlPrivate::UnhideVisibility::Hide;
1001 QQuickControlPrivate::unhideOldItem(control, vertical, visibility);
1002 }
1003
1005 vertical->setSize(area->property("heightRatio").toReal());
1006 vertical->setPosition(area->property("yPosition").toReal());
1007}
1008
1010{
1011 Q_ASSERT(flickable && horizontal);
1012
1013 QQuickControlPrivate::hideOldItem(horizontal);
1014 // ScrollBar.qml has a binding to visible and ScrollView.qml has a binding to parent.
1015 // If we just set visible to false and parent to null, these bindings will overwrite
1016 // them upon component completion as part of the binding evaluation.
1017 // That's why we remove the binding completely.
1018 const QQmlProperty visibleProperty(horizontal, QStringLiteral("visible"));
1019 const QQmlProperty parentProperty(horizontal, QStringLiteral("parent"));
1020 QQmlPropertyPrivate::removeBinding(visibleProperty);
1021 QQmlPropertyPrivate::removeBinding(parentProperty);
1022
1023 disconnect(flickable, &QQuickFlickable::movingHorizontallyChanged, this, &QQuickScrollBarAttachedPrivate::activateHorizontal);
1024
1025 // TODO: export QQuickFlickableVisibleArea
1026 QObject *area = flickable->property("visibleArea").value<QObject *>();
1027 QObject::disconnect(area, SIGNAL(widthRatioChanged(qreal)), horizontal, SLOT(setSize(qreal)));
1028 QObject::disconnect(area, SIGNAL(xPositionChanged(qreal)), horizontal, SLOT(setPosition(qreal)));
1029}
1030
1032{
1033 Q_ASSERT(flickable && vertical);
1034
1035 QQuickControlPrivate::hideOldItem(vertical);
1036 const QQmlProperty visibleProperty(vertical, QStringLiteral("visible"));
1037 const QQmlProperty parentProperty(vertical, QStringLiteral("parent"));
1038 QQmlPropertyPrivate::removeBinding(visibleProperty);
1039 QQmlPropertyPrivate::removeBinding(parentProperty);
1040
1041 disconnect(flickable, &QQuickFlickable::movingVerticallyChanged, this, &QQuickScrollBarAttachedPrivate::activateVertical);
1042
1043 // TODO: export QQuickFlickableVisibleArea
1044 QObject *area = flickable->property("visibleArea").value<QObject *>();
1045 QObject::disconnect(area, SIGNAL(heightRatioChanged(qreal)), vertical, SLOT(setSize(qreal)));
1046 QObject::disconnect(area, SIGNAL(yPositionChanged(qreal)), vertical, SLOT(setPosition(qreal)));
1047}
1048
1050{
1051 QQuickScrollBarPrivate *p = QQuickScrollBarPrivate::get(horizontal);
1052 p->moving = flickable->isMovingHorizontally();
1054}
1055
1057{
1058 QQuickScrollBarPrivate *p = QQuickScrollBarPrivate::get(vertical);
1059 p->moving = flickable->isMovingVertically();
1061}
1062
1063// TODO: QQuickFlickable::maxXYExtent()
1068
1070{
1071 if (!flickable)
1072 return;
1073
1075
1076 const qreal viewwidth = f->width();
1077 const qreal maxxextent = -f->maxXExtent() + f->minXExtent();
1078 const qreal cx = horizontal->position() * (maxxextent + viewwidth) - f->minXExtent();
1079
1080 if (!qIsNaN(cx) && !qFuzzyCompare(cx, flickable->contentX()))
1081 flickable->setContentX(cx);
1082}
1083
1085{
1086 if (!flickable)
1087 return;
1088
1090
1091 const qreal viewheight = f->height();
1092 const qreal maxyextent = -f->maxYExtent() + f->minYExtent();
1093 const qreal cy = vertical->position() * (maxyextent + viewheight) - f->minYExtent();
1094
1095 if (!qIsNaN(cy) && !qFuzzyCompare(cy, flickable->contentY()))
1096 flickable->setContentY(cy);
1097}
1098
1103
1105{
1106 Q_ASSERT(horizontal && flickable);
1107 if (horizontal->parentItem() != flickable)
1108 return;
1109 horizontal->setWidth(flickable->width());
1110 if (move)
1111 horizontal->setY(flickable->height() - horizontal->height());
1112}
1113
1115{
1116 Q_ASSERT(vertical && flickable);
1117 if (vertical->parentItem() != flickable)
1118 return;
1119 vertical->setHeight(flickable->height());
1120 if (move)
1121 vertical->setX(vertical->isMirrored() ? 0 : flickable->width() - vertical->width());
1122}
1123
1124void QQuickScrollBarAttachedPrivate::itemGeometryChanged(QQuickItem *item, const QQuickGeometryChange change, const QRectF &diff)
1125{
1126 Q_UNUSED(item);
1127 Q_UNUSED(change);
1128 if (horizontal && horizontal->height() > 0) {
1129#ifdef QT_QUICK_NEW_GEOMETRY_CHANGED_HANDLING // TODO: correct/rename diff to oldGeometry
1130 bool move = qFuzzyIsNull(horizontal->y()) || qFuzzyCompare(horizontal->y(), diff.height() - horizontal->height());
1131#else
1132 bool move = qFuzzyIsNull(horizontal->y()) || qFuzzyCompare(horizontal->y(), item->height() - diff.height() - horizontal->height());
1133#endif
1134 if (flickable)
1136 }
1137 if (vertical && vertical->width() > 0) {
1138#ifdef QT_QUICK_NEW_GEOMETRY_CHANGED_HANDLING // TODO: correct/rename diff to oldGeometry
1139 bool move = qFuzzyIsNull(vertical->x()) || qFuzzyCompare(vertical->x(), diff.width() - vertical->width());
1140#else
1141 bool move = qFuzzyIsNull(vertical->x()) || qFuzzyCompare(vertical->x(), item->width() - diff.width() - vertical->width());
1142#endif
1143 if (flickable)
1144 layoutVertical(move);
1145 }
1146}
1147
1149{
1150 if (item == vertical && flickable)
1151 layoutVertical(true);
1152}
1153
1155{
1156 if (item == horizontal && flickable)
1158}
1159
1161{
1162 if (item == flickable)
1163 flickable = nullptr;
1164 if (item == horizontal)
1165 horizontal = nullptr;
1166 if (item == vertical)
1167 vertical = nullptr;
1168}
1169
1170QQuickScrollBarAttached::QQuickScrollBarAttached(QObject *parent)
1171 : QObject(*(new QQuickScrollBarAttachedPrivate), parent)
1172{
1173 Q_D(QQuickScrollBarAttached);
1174 d->setFlickable(qobject_cast<QQuickFlickable *>(parent));
1175
1176 if (parent && !d->flickable && !qobject_cast<QQuickScrollView *>(parent))
1177 qmlWarning(parent) << "ScrollBar attached property must be attached to an object deriving from Flickable or ScrollView";
1178}
1179
1180QQuickScrollBarAttached::~QQuickScrollBarAttached()
1181{
1182 Q_D(QQuickScrollBarAttached);
1183 if (d->horizontal) {
1184 QQuickItemPrivate::get(d->horizontal)->removeItemChangeListener(d, QsbHorizontalChangeTypes);
1185 d->horizontal = nullptr;
1186 }
1187 if (d->vertical) {
1188 QQuickItemPrivate::get(d->vertical)->removeItemChangeListener(d, QsbVerticalChangeTypes);
1189 d->vertical = nullptr;
1190 }
1191 d->setFlickable(nullptr);
1192}
1193
1194/*!
1195 \qmlattachedproperty ScrollBar QtQuick.Controls::ScrollBar::horizontal
1196
1197 This property attaches a horizontal scroll bar to a \l Flickable.
1198
1199 \code
1200 Flickable {
1201 contentWidth: 2000
1202 ScrollBar.horizontal: ScrollBar { }
1203 }
1204 \endcode
1205
1206 \sa {Attaching ScrollBar to a Flickable}
1207*/
1208QQuickScrollBar *QQuickScrollBarAttached::horizontal() const
1209{
1210 Q_D(const QQuickScrollBarAttached);
1211 return d->horizontal;
1212}
1213
1214void QQuickScrollBarAttached::setHorizontal(QQuickScrollBar *horizontal)
1215{
1216 Q_D(QQuickScrollBarAttached);
1217 if (d->horizontal == horizontal)
1218 return;
1219
1220 if (d->horizontal) {
1221 QQuickItemPrivate::get(d->horizontal)->removeItemChangeListener(d, QsbHorizontalChangeTypes);
1222 QObjectPrivate::disconnect(d->horizontal, &QQuickScrollBar::positionChanged, d, &QQuickScrollBarAttachedPrivate::scrollHorizontal);
1223
1224 if (d->flickable)
1225 d->cleanupHorizontal();
1226 }
1227
1228 d->horizontal = horizontal;
1229
1230 if (horizontal) {
1231 if (!horizontal->parentItem())
1232 horizontal->setParentItem(qobject_cast<QQuickItem *>(parent()));
1233 horizontal->setOrientation(Qt::Horizontal);
1234
1235 QQuickItemPrivate::get(horizontal)->addItemChangeListener(d, QsbHorizontalChangeTypes);
1236 QObjectPrivate::connect(horizontal, &QQuickScrollBar::positionChanged, d, &QQuickScrollBarAttachedPrivate::scrollHorizontal);
1237
1238 if (d->flickable)
1239 d->initHorizontal();
1240 }
1241 emit horizontalChanged();
1242}
1243
1244/*!
1245 \qmlattachedproperty ScrollBar QtQuick.Controls::ScrollBar::vertical
1246
1247 This property attaches a vertical scroll bar to a \l Flickable.
1248
1249 \code
1250 Flickable {
1251 contentHeight: 2000
1252 ScrollBar.vertical: ScrollBar { }
1253 }
1254 \endcode
1255
1256 \sa {Attaching ScrollBar to a Flickable}
1257*/
1258QQuickScrollBar *QQuickScrollBarAttached::vertical() const
1259{
1260 Q_D(const QQuickScrollBarAttached);
1261 return d->vertical;
1262}
1263
1264void QQuickScrollBarAttached::setVertical(QQuickScrollBar *vertical)
1265{
1266 Q_D(QQuickScrollBarAttached);
1267 if (d->vertical == vertical)
1268 return;
1269
1270 if (d->vertical) {
1271 QQuickItemPrivate::get(d->vertical)->removeItemChangeListener(d, QsbVerticalChangeTypes);
1272 QObjectPrivate::disconnect(d->vertical, &QQuickScrollBar::mirroredChanged, d, &QQuickScrollBarAttachedPrivate::mirrorVertical);
1273 QObjectPrivate::disconnect(d->vertical, &QQuickScrollBar::positionChanged, d, &QQuickScrollBarAttachedPrivate::scrollVertical);
1274
1275 if (d->flickable)
1276 d->cleanupVertical();
1277 }
1278
1279 d->vertical = vertical;
1280
1281 if (vertical) {
1282 if (!vertical->parentItem())
1283 vertical->setParentItem(qobject_cast<QQuickItem *>(parent()));
1284 vertical->setOrientation(Qt::Vertical);
1285
1286 QQuickItemPrivate::get(vertical)->addItemChangeListener(d, QsbVerticalChangeTypes);
1287 QObjectPrivate::connect(vertical, &QQuickScrollBar::mirroredChanged, d, &QQuickScrollBarAttachedPrivate::mirrorVertical);
1288 QObjectPrivate::connect(vertical, &QQuickScrollBar::positionChanged, d, &QQuickScrollBarAttachedPrivate::scrollVertical);
1289
1290 if (d->flickable)
1291 d->initVertical();
1292 }
1293 emit verticalChanged();
1294}
1295
1296QT_END_NAMESPACE
1297
1298#include "moc_qquickscrollbar_p.cpp"
void itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &diff) override
void setFlickable(QQuickFlickable *flickable)
void layoutHorizontal(bool move=true)
void itemImplicitHeightChanged(QQuickItem *item) override
void itemDestroyed(QQuickItem *item) override
void itemImplicitWidthChanged(QQuickItem *item) override
void itemImplicitHeightChanged(QQuickItem *item) override
bool handlePress(const QPointF &point, ulong timestamp) override
qreal snapPosition(qreal position) const
void itemImplicitWidthChanged(QQuickItem *item) override
void updateHover(const QPointF &pos, std::optional< bool > newHoverState={})
bool handleRelease(const QPointF &point, ulong timestamp) override
bool handleMove(const QPointF &point, ulong timestamp) override
void visualAreaChange(const VisualArea &newVisualArea, const VisualArea &oldVisualArea)
qreal positionAt(const QPointF &point) const
void setPosition(qreal position, bool notifyVisualChange=true)
void setInteractive(bool interactive)
void resizeContent() override
qreal logicalPosition(qreal position) const
VisualArea visualArea() const
static QT_BEGIN_NAMESPACE const QQuickItemPrivate::ChangeTypes QsbChangeTypes
Vertical or horizontal interactive scroll bar.
static const QQuickItemPrivate::ChangeTypes QsbVerticalChangeTypes
static const QQuickItemPrivate::ChangeTypes QsbHorizontalChangeTypes