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