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
qquickscrollindicator.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
7
8#include <QtQml/qqmlinfo.h>
9#include <QtQuick/private/qquickflickable_p.h>
10#include <QtQuick/private/qquickitemchangelistener_p.h>
11
13
14/*!
15 \qmltype ScrollIndicator
16 \inherits Control
17//! \nativetype QQuickScrollIndicator
18 \inqmlmodule QtQuick.Controls
19 \since 5.7
20 \ingroup qtquickcontrols-indicators
21 \brief Vertical or horizontal non-interactive scroll indicator.
22
23 \image qtquickcontrols-scrollindicator.gif
24
25 ScrollIndicator is a non-interactive indicator that indicates the current scroll
26 position. A scroll indicator can be either \l vertical or \l horizontal, and can
27 be attached to any \l Flickable, such as \l ListView and \l GridView.
28
29 \code
30 Flickable {
31 // ...
32 ScrollIndicator.vertical: ScrollIndicator { }
33 }
34 \endcode
35
36 \section1 Attaching ScrollIndicator to a Flickable
37
38 \note When ScrollIndicator is attached \l {ScrollIndicator::vertical}{vertically}
39 or \l {ScrollIndicator::horizontal}{horizontally} to a Flickable, its geometry and
40 the following properties are automatically set and updated as appropriate:
41
42 \list
43 \li \l orientation
44 \li \l position
45 \li \l size
46 \li \l active
47 \endlist
48
49 An attached ScrollIndicator re-parents itself to the target Flickable. A vertically
50 attached ScrollIndicator resizes itself to the height of the Flickable, and positions
51 itself to either side of it based on the \l {Control::mirrored}{layout direction}.
52 A horizontally attached ScrollIndicator resizes itself to the width of the Flickable,
53 and positions itself to the bottom. The automatic geometry management can be disabled
54 by specifying another parent for the attached ScrollIndicator. This can be useful, for
55 example, if the ScrollIndicator should be placed outside a clipping Flickable. This is
56 demonstrated by the following example:
57
58 \code
59 Flickable {
60 id: flickable
61 clip: true
62 // ...
63 ScrollIndicator.vertical: ScrollIndicator {
64 parent: flickable.parent
65 anchors.top: flickable.top
66 anchors.left: flickable.right
67 anchors.bottom: flickable.bottom
68 }
69 }
70 \endcode
71
72 \section1 Binding the Active State of Horizontal and Vertical Scroll Indicators
73
74 Horizontal and vertical scroll indicators do not share the \l active state with
75 each other by default. In order to keep both indicators visible whilst scrolling
76 to either direction, establish a two-way binding between the active states as
77 presented by the following example:
78
79 \snippet qtquickcontrols-scrollindicator-active.qml 1
80
81 \section1 Non-attached Scroll Indicators
82
83 It is possible to create an instance of ScrollIndicator without using the
84 attached property API. This is useful when the behavior of the attached
85 scoll indicator is not sufficient or a \l Flickable is not in use. In the
86 following example, horizontal and vertical scroll indicators are used to
87 indicate how far the user has scrolled over the text (using \l MouseArea
88 instead of \l Flickable):
89
90 \snippet qtquickcontrols-scrollindicator-non-attached.qml 1
91
92 \image qtquickcontrols-scrollindicator-non-attached.png
93
94 \include varying-delegate-heights-section.qdocinc {file} {1} {ScrollIndicator}
95
96 \sa ScrollBar, {Customizing ScrollIndicator}, {Indicator Controls}
97*/
98
99static const QQuickItemPrivate::ChangeTypes QsiChangeTypes = QQuickItemPrivate::Geometry | QQuickItemPrivate::Destroyed;
100static const QQuickItemPrivate::ChangeTypes QsiHorizontalChangeTypes = QsiChangeTypes | QQuickItemPrivate::ImplicitHeight;
101static const QQuickItemPrivate::ChangeTypes QsiVerticalChangeTypes = QsiChangeTypes | QQuickItemPrivate::ImplicitWidth;
102
104{
105 Q_DECLARE_PUBLIC(QQuickScrollIndicator)
106
107public:
109 {
114 };
116 void visualAreaChange(const VisualArea &newVisualArea, const VisualArea &oldVisualArea);
117
119
123 bool active = false;
125};
126
128{
129 qreal visualPos = position;
130 if (minimumSize > size)
131 visualPos = position / (1.0 - size) * (1.0 - minimumSize);
132
133 qreal maximumSize = qMax<qreal>(0.0, 1.0 - visualPos);
134 qreal visualSize = qMax<qreal>(minimumSize,
135 qMin<qreal>(qMax(size, minimumSize) + qMin<qreal>(0, visualPos),
136 maximumSize));
137
138 visualPos = qMax<qreal>(0,qMin<qreal>(visualPos,qMax<qreal>(0, 1.0 - visualSize)));
139
140 return VisualArea(visualPos, visualSize);
141}
142
143void QQuickScrollIndicatorPrivate::visualAreaChange(const VisualArea &newVisualArea, const VisualArea &oldVisualArea)
144{
145 Q_Q(QQuickScrollIndicator);
146 if (!qFuzzyCompare(newVisualArea.size, oldVisualArea.size))
147 emit q->visualSizeChanged();
148 if (!qFuzzyCompare(newVisualArea.position, oldVisualArea.position))
149 emit q->visualPositionChanged();
150}
151
153{
154 Q_Q(QQuickScrollIndicator);
155 if (!contentItem)
156 return;
157
158 // - negative overshoot (pos < 0): clamp the pos to 0, and deduct the overshoot from the size
159 // - positive overshoot (pos + size > 1): clamp the size to 1-pos
160 const VisualArea visual = visualArea();
161
162 if (orientation == Qt::Horizontal) {
163 contentItem->setPosition(QPointF(q->leftPadding() + visual.position * q->availableWidth(), q->topPadding()));
164 contentItem->setSize(QSizeF(q->availableWidth() * visual.size, q->availableHeight()));
165 } else {
166 contentItem->setPosition(QPointF(q->leftPadding(), q->topPadding() + visual.position * q->availableHeight()));
167 contentItem->setSize(QSizeF(q->availableWidth(), q->availableHeight() * visual.size));
168 }
169}
170
171QQuickScrollIndicator::QQuickScrollIndicator(QQuickItem *parent)
172 : QQuickControl(*(new QQuickScrollIndicatorPrivate), parent)
173{
174 Q_D(QQuickScrollIndicator);
175 d->setSizePolicy(QLayoutPolicy::Preferred, QLayoutPolicy::Fixed);
176}
177
178QQuickScrollIndicatorAttached *QQuickScrollIndicator::qmlAttachedProperties(QObject *object)
179{
180 return new QQuickScrollIndicatorAttached(object);
181}
182
183/*!
184 \qmlproperty real QtQuick.Controls::ScrollIndicator::size
185
186 This property holds the size of the indicator, scaled to \c {0.0 - 1.0}.
187
188 \sa {Flickable::visibleArea.heightRatio}{Flickable::visibleArea}
189
190 This property is automatically set when the scroll indicator is
191 \l {Attaching ScrollIndicator to a Flickable}{attached to a flickable}.
192
193 \sa minimumSize, visualSize
194*/
195qreal QQuickScrollIndicator::size() const
196{
197 Q_D(const QQuickScrollIndicator);
198 return d->size;
199}
200
201void QQuickScrollIndicator::setSize(qreal size)
202{
203 Q_D(QQuickScrollIndicator);
204 if (qFuzzyCompare(d->size, size))
205 return;
206
207 auto oldVisualArea = d->visualArea();
208 d->size = size;
209 if (d->size + d->position > 1.0) {
210 setPosition(1.0 - d->size);
211 oldVisualArea = d->visualArea();
212 }
213 if (isComponentComplete())
214 d->resizeContent();
215 emit sizeChanged();
216 d->visualAreaChange(d->visualArea(), oldVisualArea);
217}
218
219/*!
220 \qmlproperty real QtQuick.Controls::ScrollIndicator::position
221
222 This property holds the position of the indicator, scaled to \c {0.0 - 1.0}.
223
224 This property is automatically set when the scroll indicator is
225 \l {Attaching ScrollIndicator to a Flickable}{attached to a flickable}.
226
227 \sa {Flickable::visibleArea.yPosition}{Flickable::visibleArea}, visualPosition
228*/
229qreal QQuickScrollIndicator::position() const
230{
231 Q_D(const QQuickScrollIndicator);
232 return d->position;
233}
234
235void QQuickScrollIndicator::setPosition(qreal position)
236{
237 Q_D(QQuickScrollIndicator);
238 if (qFuzzyCompare(d->position, position))
239 return;
240
241 auto oldVisualArea = d->visualArea();
242 d->position = position;
243 if (isComponentComplete())
244 d->resizeContent();
245 emit positionChanged();
246 d->visualAreaChange(d->visualArea(), oldVisualArea);
247}
248
249/*!
250 \qmlproperty bool QtQuick.Controls::ScrollIndicator::active
251
252 This property holds whether the indicator is active, that is, when the
253 attached Flickable is \l {Flickable::moving}{moving}.
254
255 It is possible to keep \l {Binding the Active State of Horizontal and Vertical Scroll Indicators}
256 {both horizontal and vertical indicators visible} while scrolling in either direction.
257
258 This property is automatically set when the scroll indicator is
259 \l {Attaching ScrollIndicator to a Flickable}{attached to a flickable}.
260*/
261bool QQuickScrollIndicator::isActive() const
262{
263 Q_D(const QQuickScrollIndicator);
264 return d->active;
265}
266
267void QQuickScrollIndicator::setActive(bool active)
268{
269 Q_D(QQuickScrollIndicator);
270 if (d->active == active)
271 return;
272
273 d->active = active;
274 emit activeChanged();
275}
276
277/*!
278 \qmlproperty enumeration QtQuick.Controls::ScrollIndicator::orientation
279
280 This property holds the orientation of the indicator.
281
282 Possible values:
283 \value Qt.Horizontal Horizontal
284 \value Qt.Vertical Vertical (default)
285
286 This property is automatically set when the scroll indicator is
287 \l {Attaching ScrollIndicator to a Flickable}{attached to a flickable}.
288
289 \sa horizontal, vertical
290*/
291Qt::Orientation QQuickScrollIndicator::orientation() const
292{
293 Q_D(const QQuickScrollIndicator);
294 return d->orientation;
295}
296
297void QQuickScrollIndicator::setOrientation(Qt::Orientation orientation)
298{
299 Q_D(QQuickScrollIndicator);
300 if (d->orientation == orientation)
301 return;
302
303 if (orientation == Qt::Horizontal)
304 d->setSizePolicy(QLayoutPolicy::Preferred, QLayoutPolicy::Fixed);
305 else
306 d->setSizePolicy(QLayoutPolicy::Fixed, QLayoutPolicy::Preferred);
307
308 d->orientation = orientation;
309 if (isComponentComplete())
310 d->resizeContent();
311 emit orientationChanged();
312}
313
314/*!
315 \since QtQuick.Controls 2.3 (Qt 5.10)
316 \qmlproperty bool QtQuick.Controls::ScrollIndicator::horizontal
317 \readonly
318
319 This property holds whether the scroll indicator is horizontal.
320
321 \sa orientation
322*/
323bool QQuickScrollIndicator::isHorizontal() const
324{
325 Q_D(const QQuickScrollIndicator);
326 return d->orientation == Qt::Horizontal;
327}
328
329/*!
330 \since QtQuick.Controls 2.3 (Qt 5.10)
331 \qmlproperty bool QtQuick.Controls::ScrollIndicator::vertical
332 \readonly
333
334 This property holds whether the scroll indicator is vertical.
335
336 \sa orientation
337*/
338bool QQuickScrollIndicator::isVertical() const
339{
340 Q_D(const QQuickScrollIndicator);
341 return d->orientation == Qt::Vertical;
342}
343
344/*!
345 \since QtQuick.Controls 2.4 (Qt 5.11)
346 \qmlproperty real QtQuick.Controls::ScrollIndicator::minimumSize
347
348 This property holds the minimum size of the indicator, scaled to \c {0.0 - 1.0}.
349
350 \sa size, visualSize, visualPosition
351*/
352qreal QQuickScrollIndicator::minimumSize() const
353{
354 Q_D(const QQuickScrollIndicator);
355 return d->minimumSize;
356}
357
358void QQuickScrollIndicator::setMinimumSize(qreal minimumSize)
359{
360 Q_D(QQuickScrollIndicator);
361 if (qFuzzyCompare(d->minimumSize, minimumSize))
362 return;
363
364 auto oldVisualArea = d->visualArea();
365 d->minimumSize = minimumSize;
366 if (isComponentComplete())
367 d->resizeContent();
368 emit minimumSizeChanged();
369 d->visualAreaChange(d->visualArea(), oldVisualArea);
370}
371
372/*!
373 \since QtQuick.Controls 2.4 (Qt 5.11)
374 \qmlproperty real QtQuick.Controls::ScrollIndicator::visualSize
375
376 This property holds the effective visual size of the indicator,
377 which may be limited by the \l {minimumSize}{minimum size}.
378
379 \sa size, minimumSize
380*/
381qreal QQuickScrollIndicator::visualSize() const
382{
383 Q_D(const QQuickScrollIndicator);
384 return d->visualArea().size;
385}
386
387/*!
388 \since QtQuick.Controls 2.4 (Qt 5.11)
389 \qmlproperty real QtQuick.Controls::ScrollIndicator::visualPosition
390
391 This property holds the effective visual position of the indicator,
392 which may be limited by the \l {minimumSize}{minimum size}.
393
394 \sa position, minimumSize
395*/
396qreal QQuickScrollIndicator::visualPosition() const
397{
398 Q_D(const QQuickScrollIndicator);
399 return d->visualArea().position;
400}
401
404{
405public:
408
409 void layoutHorizontal(bool move = true);
410 void layoutVertical(bool move = true);
411
412 void itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &diff) override;
413 void itemImplicitWidthChanged(QQuickItem *item) override;
414 void itemImplicitHeightChanged(QQuickItem *item) override;
415 void itemDestroyed(QQuickItem *item) override;
416
417 QQuickFlickable *flickable = nullptr;
420};
421
423{
424 horizontal->setActive(flickable->isMovingHorizontally());
425}
426
428{
429 vertical->setActive(flickable->isMovingVertically());
430}
431
433{
434 Q_ASSERT(horizontal && flickable);
435 if (horizontal->parentItem() != flickable)
436 return;
437 horizontal->setWidth(flickable->width());
438 if (move)
439 horizontal->setY(flickable->height() - horizontal->height());
440}
441
443{
444 Q_ASSERT(vertical && flickable);
445 if (vertical->parentItem() != flickable)
446 return;
447 vertical->setHeight(flickable->height());
448 if (move && !QQuickItemPrivate::get(vertical)->isMirrored())
449 vertical->setX(flickable->width() - vertical->width());
450}
451
452void QQuickScrollIndicatorAttachedPrivate::itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &diff)
453{
454 Q_UNUSED(item);
455 Q_UNUSED(change);
456 if (horizontal && horizontal->height() > 0) {
457#ifdef QT_QUICK_NEW_GEOMETRY_CHANGED_HANDLING // TODO: correct/rename diff to oldGeometry
458 bool move = qFuzzyIsNull(horizontal->y()) || qFuzzyCompare(horizontal->y(), diff.height() - horizontal->height());
459#else
460 bool move = qFuzzyIsNull(horizontal->y()) || qFuzzyCompare(horizontal->y(), item->height() - diff.height() - horizontal->height());
461#endif
463 }
464 if (vertical && vertical->width() > 0) {
465#ifdef QT_QUICK_NEW_GEOMETRY_CHANGED_HANDLING // TODO: correct/rename diff to oldGeometry
466 bool move = qFuzzyIsNull(vertical->x()) || qFuzzyCompare(vertical->x(), diff.width() - vertical->width());
467#else
468 bool move = qFuzzyIsNull(vertical->x()) || qFuzzyCompare(vertical->x(), item->width() - diff.width() - vertical->width());
469#endif
471 }
472}
473
475{
476 if (item == vertical)
478}
479
481{
482 if (item == horizontal)
484}
485
487{
488 if (item == horizontal)
489 horizontal = nullptr;
490 if (item == vertical)
491 vertical = nullptr;
492}
493
494QQuickScrollIndicatorAttached::QQuickScrollIndicatorAttached(QObject *parent)
495 : QObject(*(new QQuickScrollIndicatorAttachedPrivate), parent)
496{
497 Q_D(QQuickScrollIndicatorAttached);
498 d->flickable = qobject_cast<QQuickFlickable *>(parent);
499 if (d->flickable)
500 QQuickItemPrivate::get(d->flickable)->updateOrAddGeometryChangeListener(d, QQuickGeometryChange::Size);
501 else if (parent)
502 qmlWarning(parent) << "ScrollIndicator attached property must be attached to an object deriving from Flickable";
503}
504
505QQuickScrollIndicatorAttached::~QQuickScrollIndicatorAttached()
506{
507 Q_D(QQuickScrollIndicatorAttached);
508 if (d->flickable) {
509 if (d->horizontal)
510 QQuickItemPrivate::get(d->horizontal)->removeItemChangeListener(d, QsiHorizontalChangeTypes);
511 if (d->vertical)
512 QQuickItemPrivate::get(d->vertical)->removeItemChangeListener(d, QsiVerticalChangeTypes);
513 // NOTE: Use removeItemChangeListener(Geometry) instead of updateOrRemoveGeometryChangeListener(Size).
514 // The latter doesn't remove the listener but only resets its types. Thus, it leaves behind a dangling
515 // pointer on destruction.
516 QQuickItemPrivate::get(d->flickable)->removeItemChangeListener(d, QQuickItemPrivate::Geometry);
517 }
518}
519
520/*!
521 \qmlattachedproperty ScrollIndicator QtQuick.Controls::ScrollIndicator::horizontal
522
523 This property attaches a horizontal scroll indicator to a \l Flickable.
524
525 \code
526 Flickable {
527 contentWidth: 2000
528 ScrollIndicator.horizontal: ScrollIndicator { }
529 }
530 \endcode
531
532 \sa {Attaching ScrollIndicator to a Flickable}
533*/
534QQuickScrollIndicator *QQuickScrollIndicatorAttached::horizontal() const
535{
536 Q_D(const QQuickScrollIndicatorAttached);
537 return d->horizontal;
538}
539
540void QQuickScrollIndicatorAttached::setHorizontal(QQuickScrollIndicator *horizontal)
541{
542 Q_D(QQuickScrollIndicatorAttached);
543 if (d->horizontal == horizontal)
544 return;
545
546 if (d->horizontal && d->flickable) {
547 QQuickItemPrivate::get(d->horizontal)->removeItemChangeListener(d, QsiHorizontalChangeTypes);
548 QObjectPrivate::disconnect(d->flickable, &QQuickFlickable::movingHorizontallyChanged, d, &QQuickScrollIndicatorAttachedPrivate::activateHorizontal);
549
550 // TODO: export QQuickFlickableVisibleArea
551 QObject *area = d->flickable->property("visibleArea").value<QObject *>();
552 disconnect(area, SIGNAL(widthRatioChanged(qreal)), d->horizontal, SLOT(setSize(qreal)));
553 disconnect(area, SIGNAL(xPositionChanged(qreal)), d->horizontal, SLOT(setPosition(qreal)));
554 }
555
556 d->horizontal = horizontal;
557
558 if (horizontal && d->flickable) {
559 if (!horizontal->parentItem())
560 horizontal->setParentItem(d->flickable);
561 horizontal->setOrientation(Qt::Horizontal);
562
563 QQuickItemPrivate::get(horizontal)->addItemChangeListener(d, QsiHorizontalChangeTypes);
564 QObjectPrivate::connect(d->flickable, &QQuickFlickable::movingHorizontallyChanged, d, &QQuickScrollIndicatorAttachedPrivate::activateHorizontal);
565
566 // TODO: export QQuickFlickableVisibleArea
567 QObject *area = d->flickable->property("visibleArea").value<QObject *>();
568 connect(area, SIGNAL(widthRatioChanged(qreal)), horizontal, SLOT(setSize(qreal)));
569 connect(area, SIGNAL(xPositionChanged(qreal)), horizontal, SLOT(setPosition(qreal)));
570
571 d->layoutHorizontal();
572 horizontal->setSize(area->property("widthRatio").toReal());
573 horizontal->setPosition(area->property("xPosition").toReal());
574 }
575 emit horizontalChanged();
576}
577
578/*!
579 \qmlattachedproperty ScrollIndicator QtQuick.Controls::ScrollIndicator::vertical
580
581 This property attaches a vertical scroll indicator to a \l Flickable.
582
583 \code
584 Flickable {
585 contentHeight: 2000
586 ScrollIndicator.vertical: ScrollIndicator { }
587 }
588 \endcode
589
590 \sa {Attaching ScrollIndicator to a Flickable}
591*/
592QQuickScrollIndicator *QQuickScrollIndicatorAttached::vertical() const
593{
594 Q_D(const QQuickScrollIndicatorAttached);
595 return d->vertical;
596}
597
598void QQuickScrollIndicatorAttached::setVertical(QQuickScrollIndicator *vertical)
599{
600 Q_D(QQuickScrollIndicatorAttached);
601 if (d->vertical == vertical)
602 return;
603
604 if (d->vertical && d->flickable) {
605 QQuickItemPrivate::get(d->vertical)->removeItemChangeListener(d, QsiVerticalChangeTypes);
606 QObjectPrivate::disconnect(d->flickable, &QQuickFlickable::movingVerticallyChanged, d, &QQuickScrollIndicatorAttachedPrivate::activateVertical);
607
608 // TODO: export QQuickFlickableVisibleArea
609 QObject *area = d->flickable->property("visibleArea").value<QObject *>();
610 disconnect(area, SIGNAL(heightRatioChanged(qreal)), d->vertical, SLOT(setSize(qreal)));
611 disconnect(area, SIGNAL(yPositionChanged(qreal)), d->vertical, SLOT(setPosition(qreal)));
612 }
613
614 d->vertical = vertical;
615
616 if (vertical && d->flickable) {
617 if (!vertical->parentItem())
618 vertical->setParentItem(d->flickable);
619 vertical->setOrientation(Qt::Vertical);
620
621 QQuickItemPrivate::get(vertical)->addItemChangeListener(d, QsiVerticalChangeTypes);
622 QObjectPrivate::connect(d->flickable, &QQuickFlickable::movingVerticallyChanged, d, &QQuickScrollIndicatorAttachedPrivate::activateVertical);
623
624 // TODO: export QQuickFlickableVisibleArea
625 QObject *area = d->flickable->property("visibleArea").value<QObject *>();
626 connect(area, SIGNAL(heightRatioChanged(qreal)), vertical, SLOT(setSize(qreal)));
627 connect(area, SIGNAL(yPositionChanged(qreal)), vertical, SLOT(setPosition(qreal)));
628
629 d->layoutVertical();
630 vertical->setSize(area->property("heightRatio").toReal());
631 vertical->setPosition(area->property("yPosition").toReal());
632 }
633 emit verticalChanged();
634}
635
636#if QT_CONFIG(quicktemplates2_multitouch)
637void QQuickScrollIndicator::touchEvent(QTouchEvent *event)
638{
639 event->ignore(); // QTBUG-61785
640}
641#endif
642
643#if QT_CONFIG(accessibility)
644QAccessible::Role QQuickScrollIndicator::accessibleRole() const
645{
646 return QAccessible::Indicator;
647}
648#endif
649
650QT_END_NAMESPACE
651
652#include "moc_qquickscrollindicator_p.cpp"
void itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &diff) override
void itemImplicitHeightChanged(QQuickItem *item) override
void itemImplicitWidthChanged(QQuickItem *item) override
void itemDestroyed(QQuickItem *item) override
void visualAreaChange(const VisualArea &newVisualArea, const VisualArea &oldVisualArea)
static QT_BEGIN_NAMESPACE const QQuickItemPrivate::ChangeTypes QsiChangeTypes
Vertical or horizontal non-interactive scroll indicator.
static const QQuickItemPrivate::ChangeTypes QsiVerticalChangeTypes
static const QQuickItemPrivate::ChangeTypes QsiHorizontalChangeTypes