1/****************************************************************************
2**
3** Copyright (C) 2017 The Qt Company Ltd.
4** Contact: http://www.qt.io/licensing/
5**
6** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL3$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see http://www.qt.io/terms-conditions. For further
15** information use the contact form at http://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPLv3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or later as published by the Free
28** Software Foundation and appearing in the file LICENSE.GPL included in
29** the packaging of this file. Please review the following information to
30** ensure the GNU General Public License version 2.0 requirements will be
31** met: http://www.gnu.org/licenses/gpl-2.0.html.
32**
33** $QT_END_LICENSE$
34**
35****************************************************************************/
36
37#include "qquickscrollbar_p.h"
38#include "qquickscrollbar_p_p.h"
39#include "qquickscrollview_p.h"
40
41#include <QtQml/qqmlinfo.h>
42#include <QtQuick/private/qquickflickable_p.h>
43#if QT_CONFIG(accessibility)
44#include <QtQuick/private/qquickaccessibleattached_p.h>
45#endif
46
47QT_BEGIN_NAMESPACE
48
49/*!
50 \qmltype ScrollBar
51 \inherits Control
52//! \instantiates QQuickScrollBar
53 \inqmlmodule QtQuick.Controls
54 \since 5.7
55 \ingroup qtquickcontrols2-indicators
56 \brief Vertical or horizontal interactive scroll bar.
57
58 \image qtquickcontrols2-scrollbar.gif
59
60 ScrollBar is an interactive bar that can be used to scroll to a specific
61 position. A scroll bar can be either \l vertical or \l horizontal, and can
62 be attached to any \l Flickable, such as \l ListView and \l GridView.
63
64 \code
65 Flickable {
66 // ...
67 ScrollBar.vertical: ScrollBar { }
68 }
69 \endcode
70
71 \section1 Attaching ScrollBar to a Flickable
72
73 When ScrollBar is attached \l {ScrollBar::vertical}{vertically} or
74 \l {ScrollBar::horizontal}{horizontally} to a Flickable, its geometry and
75 the following properties are automatically set and updated as appropriate:
76
77 \list
78 \li \l orientation
79 \li \l position
80 \li \l size
81 \li \l active
82 \endlist
83
84 An attached ScrollBar re-parents itself to the target Flickable. A vertically
85 attached ScrollBar resizes itself to the height of the Flickable, and positions
86 itself to either side of it based on the \l {Control::mirrored}{layout direction}.
87 A horizontally attached ScrollBar resizes itself to the width of the Flickable,
88 and positions itself to the bottom. The automatic geometry management can be disabled
89 by specifying another parent for the attached ScrollBar. This can be useful, for
90 example, if the ScrollBar should be placed outside a clipping Flickable. This is
91 demonstrated by the following example:
92
93 \code
94 Flickable {
95 id: flickable
96 clip: true
97 // ...
98 ScrollBar.vertical: ScrollBar {
99 parent: flickable.parent
100 anchors.top: flickable.top
101 anchors.left: flickable.right
102 anchors.bottom: flickable.bottom
103 }
104 }
105 \endcode
106
107 Notice that ScrollBar does not filter key events of the Flickable it is
108 attached to. The following example illustrates how to implement scrolling
109 with up and down keys:
110
111 \code
112 Flickable {
113 focus: true
114
115 Keys.onUpPressed: scrollBar.decrease()
116 Keys.onDownPressed: scrollBar.increase()
117
118 ScrollBar.vertical: ScrollBar { id: scrollBar }
119 }
120 \endcode
121
122 \section1 Binding the Active State of Horizontal and Vertical Scroll Bars
123
124 Horizontal and vertical scroll bars do not share the \l active state with
125 each other by default. In order to keep both bars visible whilst scrolling
126 to either direction, establish a two-way binding between the active states
127 as presented by the following example:
128
129 \snippet qtquickcontrols2-scrollbar-active.qml 1
130
131 \section1 Non-attached Scroll Bars
132
133 It is possible to create an instance of ScrollBar without using the
134 attached property API. This is useful when the behavior of the attached
135 scroll bar is not sufficient or a \l Flickable is not in use. In the
136 following example, horizontal and vertical scroll bars are used to
137 scroll over the text without using \l Flickable:
138
139 \snippet qtquickcontrols2-scrollbar-non-attached.qml 1
140
141 \image qtquickcontrols2-scrollbar-non-attached.png
142
143 When using a non-attached ScrollBar, the following must be done manually:
144
145 \list
146 \li Layout the scroll bar (with the \l {Item::}{x} and \l {Item::}{y} or
147 \l [QtQuick]{Item::}{anchors} property, for example).
148 \li Set the \l size and \l position properties to determine the size and position
149 of the scroll bar in relation to the scrolled item.
150 \li Set the \l active property to determine when the scroll bar will be
151 visible.
152 \endlist
153
154 \sa ScrollIndicator, {Customizing ScrollBar}, {Indicator Controls}
155*/
156
157static const QQuickItemPrivate::ChangeTypes changeTypes = QQuickItemPrivate::Geometry | QQuickItemPrivate::Destroyed;
158static const QQuickItemPrivate::ChangeTypes horizontalChangeTypes = changeTypes | QQuickItemPrivate::ImplicitHeight;
159static const QQuickItemPrivate::ChangeTypes verticalChangeTypes = changeTypes | QQuickItemPrivate::ImplicitWidth;
160
161QQuickScrollBarPrivate::VisualArea QQuickScrollBarPrivate::visualArea() const
162{
163 qreal visualPos = position;
164 if (minimumSize > size)
165 visualPos = position / (1.0 - size) * (1.0 - minimumSize);
166
167 qreal visualSize = qBound<qreal>(min: 0, val: qMax(a: size, b: minimumSize) + qMin<qreal>(a: 0, b: visualPos), max: 1.0 - visualPos);
168
169 visualPos = qBound<qreal>(min: 0, val: visualPos, max: 1.0 - visualSize);
170
171 return VisualArea(visualPos, visualSize);
172}
173
174qreal QQuickScrollBarPrivate::logicalPosition(qreal position) const
175{
176 if (minimumSize > size)
177 return position * (1.0 - size) / (1.0 - minimumSize);
178 return position;
179}
180
181qreal QQuickScrollBarPrivate::snapPosition(qreal position) const
182{
183 const qreal effectiveStep = stepSize * (1.0 - size);
184 if (qFuzzyIsNull(d: effectiveStep))
185 return position;
186
187 return qRound(d: position / effectiveStep) * effectiveStep;
188}
189
190qreal QQuickScrollBarPrivate::positionAt(const QPointF &point) const
191{
192 Q_Q(const QQuickScrollBar);
193 if (orientation == Qt::Horizontal)
194 return logicalPosition(position: point.x() - q->leftPadding()) / q->availableWidth();
195 else
196 return logicalPosition(position: point.y() - q->topPadding()) / q->availableHeight();
197}
198
199void QQuickScrollBarPrivate::setInteractive(bool enabled)
200{
201 Q_Q(QQuickScrollBar);
202 if (interactive == enabled)
203 return;
204
205 interactive = enabled;
206 if (interactive) {
207 q->setAcceptedMouseButtons(Qt::LeftButton);
208#if QT_CONFIG(quicktemplates2_multitouch)
209 q->setAcceptTouchEvents(true);
210#endif
211#if QT_CONFIG(cursor)
212 q->setCursor(Qt::ArrowCursor);
213#endif
214 } else {
215 q->setAcceptedMouseButtons(Qt::NoButton);
216#if QT_CONFIG(quicktemplates2_multitouch)
217 q->setAcceptTouchEvents(false);
218#endif
219#if QT_CONFIG(cursor)
220 q->unsetCursor();
221#endif
222 q->ungrabMouse();
223 }
224 emit q->interactiveChanged();
225}
226
227void QQuickScrollBarPrivate::updateActive()
228{
229 Q_Q(QQuickScrollBar);
230#if QT_CONFIG(quicktemplates2_hover)
231 bool hover = hovered;
232#else
233 bool hover = false;
234#endif
235 q->setActive(moving || (interactive && (pressed || hover)));
236}
237
238void QQuickScrollBarPrivate::resizeContent()
239{
240 Q_Q(QQuickScrollBar);
241 if (!contentItem)
242 return;
243
244 // - negative overshoot (pos < 0): clamp the pos to 0, and deduct the overshoot from the size
245 // - positive overshoot (pos + size > 1): clamp the size to 1-pos
246 const VisualArea visual = visualArea();
247
248 if (orientation == Qt::Horizontal) {
249 contentItem->setPosition(QPointF(q->leftPadding() + visual.position * q->availableWidth(), q->topPadding()));
250 contentItem->setSize(QSizeF(q->availableWidth() * visual.size, q->availableHeight()));
251 } else {
252 contentItem->setPosition(QPointF(q->leftPadding(), q->topPadding() + visual.position * q->availableHeight()));
253 contentItem->setSize(QSizeF(q->availableWidth(), q->availableHeight() * visual.size));
254 }
255}
256
257void QQuickScrollBarPrivate::handlePress(const QPointF &point)
258{
259 Q_Q(QQuickScrollBar);
260 QQuickControlPrivate::handlePress(point);
261 offset = positionAt(point) - position;
262 qreal sz = qMax(a: size, b: logicalPosition(position: minimumSize));
263 if (offset < 0 || offset > sz)
264 offset = sz / 2;
265 q->setPressed(true);
266}
267
268void QQuickScrollBarPrivate::handleMove(const QPointF &point)
269{
270 Q_Q(QQuickScrollBar);
271 QQuickControlPrivate::handleMove(point);
272 qreal pos = qBound<qreal>(min: 0.0, val: positionAt(point) - offset, max: 1.0 - size);
273 if (snapMode == QQuickScrollBar::SnapAlways)
274 pos = snapPosition(position: pos);
275 q->setPosition(pos);
276}
277
278void QQuickScrollBarPrivate::handleRelease(const QPointF &point)
279{
280 Q_Q(QQuickScrollBar);
281 QQuickControlPrivate::handleRelease(point);
282 qreal pos = qBound<qreal>(min: 0.0, val: positionAt(point) - offset, max: 1.0 - size);
283 if (snapMode != QQuickScrollBar::NoSnap)
284 pos = snapPosition(position: pos);
285 q->setPosition(pos);
286 offset = 0.0;
287 q->setPressed(false);
288}
289
290void QQuickScrollBarPrivate::handleUngrab()
291{
292 Q_Q(QQuickScrollBar);
293 QQuickControlPrivate::handleUngrab();
294 offset = 0.0;
295 q->setPressed(false);
296}
297
298void QQuickScrollBarPrivate::visualAreaChange(const VisualArea &newVisualArea, const VisualArea &oldVisualArea)
299{
300 Q_Q(QQuickScrollBar);
301 if (!qFuzzyCompare(p1: newVisualArea.size, p2: oldVisualArea.size))
302 emit q->visualSizeChanged();
303 if (!qFuzzyCompare(p1: newVisualArea.position, p2: oldVisualArea.position))
304 emit q->visualPositionChanged();
305}
306
307QQuickScrollBar::QQuickScrollBar(QQuickItem *parent)
308 : QQuickControl(*(new QQuickScrollBarPrivate), parent)
309{
310 setKeepMouseGrab(true);
311 setAcceptedMouseButtons(Qt::LeftButton);
312#if QT_CONFIG(quicktemplates2_multitouch)
313 setAcceptTouchEvents(true);
314#endif
315#if QT_CONFIG(cursor)
316 setCursor(Qt::ArrowCursor);
317#endif
318}
319
320QQuickScrollBarAttached *QQuickScrollBar::qmlAttachedProperties(QObject *object)
321{
322 return new QQuickScrollBarAttached(object);
323}
324
325/*!
326 \qmlproperty real QtQuick.Controls::ScrollBar::size
327
328 This property holds the size of the scroll bar, scaled to \c {0.0 - 1.0}.
329
330 \sa {Flickable::visibleArea.heightRatio}{Flickable::visibleArea}
331
332 This property is automatically set when the scroll bar is
333 \l {Attaching ScrollBar to a Flickable}{attached to a flickable}.
334
335 \sa minimumSize, visualSize
336*/
337qreal QQuickScrollBar::size() const
338{
339 Q_D(const QQuickScrollBar);
340 return d->size;
341}
342
343void QQuickScrollBar::setSize(qreal size)
344{
345 Q_D(QQuickScrollBar);
346 if (qFuzzyCompare(p1: d->size, p2: size))
347 return;
348
349 auto oldVisualArea = d->visualArea();
350 d->size = size;
351 if (isComponentComplete())
352 d->resizeContent();
353 emit sizeChanged();
354 d->visualAreaChange(newVisualArea: d->visualArea(), oldVisualArea);
355}
356
357/*!
358 \qmlproperty real QtQuick.Controls::ScrollBar::position
359
360 This property holds the position of the scroll bar, scaled to \c {0.0 - 1.0}.
361
362 \sa {Flickable::visibleArea.yPosition}{Flickable::visibleArea}
363
364 This property is automatically set when the scroll bar is
365 \l {Attaching ScrollBar to a Flickable}{attached to a flickable}.
366
367 \sa visualPosition
368*/
369qreal QQuickScrollBar::position() const
370{
371 Q_D(const QQuickScrollBar);
372 return d->position;
373}
374
375void QQuickScrollBar::setPosition(qreal position)
376{
377 Q_D(QQuickScrollBar);
378 if (qFuzzyCompare(p1: d->position, p2: position))
379 return;
380
381 auto oldVisualArea = d->visualArea();
382 d->position = position;
383 if (isComponentComplete())
384 d->resizeContent();
385 emit positionChanged();
386 d->visualAreaChange(newVisualArea: d->visualArea(), oldVisualArea);
387}
388
389/*!
390 \qmlproperty real QtQuick.Controls::ScrollBar::stepSize
391
392 This property holds the step size. The default value is \c 0.0.
393
394 \sa snapMode, increase(), decrease()
395*/
396qreal QQuickScrollBar::stepSize() const
397{
398 Q_D(const QQuickScrollBar);
399 return d->stepSize;
400}
401
402void QQuickScrollBar::setStepSize(qreal step)
403{
404 Q_D(QQuickScrollBar);
405 if (qFuzzyCompare(p1: d->stepSize, p2: step))
406 return;
407
408 d->stepSize = step;
409 emit stepSizeChanged();
410}
411
412/*!
413 \qmlproperty bool QtQuick.Controls::ScrollBar::active
414
415 This property holds whether the scroll bar is active, i.e. when it's \l pressed
416 or the attached Flickable is \l {Flickable::moving}{moving}.
417
418 It is possible to keep \l {Binding the Active State of Horizontal and Vertical Scroll Bars}
419 {both horizontal and vertical bars visible} while scrolling in either direction.
420
421 This property is automatically set when the scroll bar is
422 \l {Attaching ScrollBar to a Flickable}{attached to a flickable}.
423*/
424bool QQuickScrollBar::isActive() const
425{
426 Q_D(const QQuickScrollBar);
427 return d->active;
428}
429
430void QQuickScrollBar::setActive(bool active)
431{
432 Q_D(QQuickScrollBar);
433 if (d->active == active)
434 return;
435
436 d->active = active;
437 emit activeChanged();
438}
439
440/*!
441 \qmlproperty bool QtQuick.Controls::ScrollBar::pressed
442
443 This property holds whether the scroll bar is pressed.
444*/
445bool QQuickScrollBar::isPressed() const
446{
447 Q_D(const QQuickScrollBar);
448 return d->pressed;
449}
450
451void QQuickScrollBar::setPressed(bool pressed)
452{
453 Q_D(QQuickScrollBar);
454 if (d->pressed == pressed)
455 return;
456
457 d->pressed = pressed;
458 setAccessibleProperty(propertyName: "pressed", value: pressed);
459 d->updateActive();
460 emit pressedChanged();
461}
462
463/*!
464 \qmlproperty enumeration QtQuick.Controls::ScrollBar::orientation
465
466 This property holds the orientation of the scroll bar.
467
468 Possible values:
469 \value Qt.Horizontal Horizontal
470 \value Qt.Vertical Vertical (default)
471
472 This property is automatically set when the scroll bar is
473 \l {Attaching ScrollBar to a Flickable}{attached to a flickable}.
474
475 \sa horizontal, vertical
476*/
477Qt::Orientation QQuickScrollBar::orientation() const
478{
479 Q_D(const QQuickScrollBar);
480 return d->orientation;
481}
482
483void QQuickScrollBar::setOrientation(Qt::Orientation orientation)
484{
485 Q_D(QQuickScrollBar);
486 if (d->orientation == orientation)
487 return;
488
489 d->orientation = orientation;
490 if (isComponentComplete())
491 d->resizeContent();
492 emit orientationChanged();
493}
494
495/*!
496 \since QtQuick.Controls 2.2 (Qt 5.9)
497 \qmlproperty enumeration QtQuick.Controls::ScrollBar::snapMode
498
499 This property holds the snap mode.
500
501 Possible values:
502 \value ScrollBar.NoSnap The scrollbar does not snap (default).
503 \value ScrollBar.SnapAlways The scrollbar snaps while dragged.
504 \value ScrollBar.SnapOnRelease The scrollbar does not snap while being dragged, but only after released.
505
506 In the following table, the various modes are illustrated with animations.
507 The movement and the \l stepSize (\c 0.25) are identical in each animation.
508
509 \table
510 \header
511 \row \li \b Value \li \b Example
512 \row \li \c ScrollBar.NoSnap \li \image qtquickcontrols2-scrollbar-nosnap.gif
513 \row \li \c ScrollBar.SnapAlways \li \image qtquickcontrols2-scrollbar-snapalways.gif
514 \row \li \c ScrollBar.SnapOnRelease \li \image qtquickcontrols2-scrollbar-snaponrelease.gif
515 \endtable
516
517 \sa stepSize
518*/
519QQuickScrollBar::SnapMode QQuickScrollBar::snapMode() const
520{
521 Q_D(const QQuickScrollBar);
522 return d->snapMode;
523}
524
525void QQuickScrollBar::setSnapMode(SnapMode mode)
526{
527 Q_D(QQuickScrollBar);
528 if (d->snapMode == mode)
529 return;
530
531 d->snapMode = mode;
532 emit snapModeChanged();
533}
534
535/*!
536 \since QtQuick.Controls 2.2 (Qt 5.9)
537 \qmlproperty bool QtQuick.Controls::ScrollBar::interactive
538
539 This property holds whether the scroll bar is interactive. The default value is \c true.
540
541 A non-interactive scroll bar is visually and behaviorally similar to \l ScrollIndicator.
542 This property is useful for switching between typical mouse- and touch-orientated UIs
543 with interactive and non-interactive scroll bars, respectively.
544*/
545bool QQuickScrollBar::isInteractive() const
546{
547 Q_D(const QQuickScrollBar);
548 return d->interactive;
549}
550
551void QQuickScrollBar::setInteractive(bool interactive)
552{
553 Q_D(QQuickScrollBar);
554 d->explicitInteractive = true;
555 d->setInteractive(interactive);
556}
557
558void QQuickScrollBar::resetInteractive()
559{
560 Q_D(QQuickScrollBar);
561 d->explicitInteractive = false;
562 d->setInteractive(true);
563}
564
565/*!
566 \since QtQuick.Controls 2.2 (Qt 5.9)
567 \qmlproperty enumeration QtQuick.Controls::ScrollBar::policy
568
569 This property holds the policy of the scroll bar. The default policy is \c ScrollBar.AsNeeded.
570
571 Possible values:
572 \value ScrollBar.AsNeeded The scroll bar is only shown when the content is too large to fit.
573 \value ScrollBar.AlwaysOff The scroll bar is never shown.
574 \value ScrollBar.AlwaysOn The scroll bar is always shown.
575
576 The following example keeps the vertical scroll bar always visible:
577
578 \snippet qtquickcontrols2-scrollbar-policy.qml 1
579*/
580QQuickScrollBar::Policy QQuickScrollBar::policy() const
581{
582 Q_D(const QQuickScrollBar);
583 return d->policy;
584}
585
586void QQuickScrollBar::setPolicy(Policy policy)
587{
588 Q_D(QQuickScrollBar);
589 if (d->policy == policy)
590 return;
591
592 d->policy = policy;
593 emit policyChanged();
594}
595
596/*!
597 \since QtQuick.Controls 2.3 (Qt 5.10)
598 \qmlproperty bool QtQuick.Controls::ScrollBar::horizontal
599 \readonly
600
601 This property holds whether the scroll bar is horizontal.
602
603 \sa orientation
604*/
605bool QQuickScrollBar::isHorizontal() const
606{
607 Q_D(const QQuickScrollBar);
608 return d->orientation == Qt::Horizontal;
609}
610
611/*!
612 \since QtQuick.Controls 2.3 (Qt 5.10)
613 \qmlproperty bool QtQuick.Controls::ScrollBar::vertical
614 \readonly
615
616 This property holds whether the scroll bar is vertical.
617
618 \sa orientation
619*/
620bool QQuickScrollBar::isVertical() const
621{
622 Q_D(const QQuickScrollBar);
623 return d->orientation == Qt::Vertical;
624}
625
626/*!
627 \since QtQuick.Controls 2.4 (Qt 5.11)
628 \qmlproperty real QtQuick.Controls::ScrollBar::minimumSize
629
630 This property holds the minimum size of the scroll bar, scaled to \c {0.0 - 1.0}.
631
632 \sa size, visualSize, visualPosition
633*/
634qreal QQuickScrollBar::minimumSize() const
635{
636 Q_D(const QQuickScrollBar);
637 return d->minimumSize;
638}
639
640void QQuickScrollBar::setMinimumSize(qreal minimumSize)
641{
642 Q_D(QQuickScrollBar);
643 if (qFuzzyCompare(p1: d->minimumSize, p2: minimumSize))
644 return;
645
646 auto oldVisualArea = d->visualArea();
647 d->minimumSize = minimumSize;
648 if (isComponentComplete())
649 d->resizeContent();
650 emit minimumSizeChanged();
651 d->visualAreaChange(newVisualArea: d->visualArea(), oldVisualArea);
652}
653
654/*!
655 \since QtQuick.Controls 2.4 (Qt 5.11)
656 \qmlproperty real QtQuick.Controls::ScrollBar::visualSize
657
658 This property holds the effective visual size of the scroll bar,
659 which may be limited by the \l {minimumSize}{minimum size}.
660
661 \sa size, minimumSize
662*/
663qreal QQuickScrollBar::visualSize() const
664{
665 Q_D(const QQuickScrollBar);
666 return d->visualArea().size;
667}
668
669/*!
670 \since QtQuick.Controls 2.4 (Qt 5.11)
671 \qmlproperty real QtQuick.Controls::ScrollBar::visualPosition
672
673 This property holds the effective visual position of the scroll bar,
674 which may be limited by the \l {minimumSize}{minimum size}.
675
676 \sa position, minimumSize
677*/
678qreal QQuickScrollBar::visualPosition() const
679{
680 Q_D(const QQuickScrollBar);
681 return d->visualArea().position;
682}
683
684/*!
685 \qmlmethod void QtQuick.Controls::ScrollBar::increase()
686
687 Increases the position by \l stepSize or \c 0.1 if stepSize is \c 0.0.
688
689 \sa stepSize
690*/
691void QQuickScrollBar::increase()
692{
693 Q_D(QQuickScrollBar);
694 qreal step = qFuzzyIsNull(d: d->stepSize) ? 0.1 : d->stepSize;
695 bool wasActive = d->active;
696 setActive(true);
697 setPosition(qMin<qreal>(a: 1.0 - d->size, b: d->position + step));
698 setActive(wasActive);
699}
700
701/*!
702 \qmlmethod void QtQuick.Controls::ScrollBar::decrease()
703
704 Decreases the position by \l stepSize or \c 0.1 if stepSize is \c 0.0.
705
706 \sa stepSize
707*/
708void QQuickScrollBar::decrease()
709{
710 Q_D(QQuickScrollBar);
711 qreal step = qFuzzyIsNull(d: d->stepSize) ? 0.1 : d->stepSize;
712 bool wasActive = d->active;
713 setActive(true);
714 setPosition(qMax<qreal>(a: 0.0, b: d->position - step));
715 setActive(wasActive);
716}
717
718void QQuickScrollBar::mousePressEvent(QMouseEvent *event)
719{
720 Q_D(QQuickScrollBar);
721 QQuickControl::mousePressEvent(event);
722 d->handleMove(point: event->localPos());
723}
724
725#if QT_CONFIG(quicktemplates2_hover)
726void QQuickScrollBar::hoverChange()
727{
728 Q_D(QQuickScrollBar);
729 d->updateActive();
730}
731#endif
732
733#if QT_CONFIG(accessibility)
734void QQuickScrollBar::accessibilityActiveChanged(bool active)
735{
736 QQuickControl::accessibilityActiveChanged(active);
737
738 Q_D(QQuickScrollBar);
739 if (active) {
740 setAccessibleProperty(propertyName: "pressed", value: d->pressed);
741
742 if (QQuickAccessibleAttached *accessibleAttached = QQuickControlPrivate::accessibleAttached(object: this)) {
743 connect(sender: accessibleAttached, signal: &QQuickAccessibleAttached::increaseAction, receiver: this, slot: &QQuickScrollBar::increase);
744 connect(sender: accessibleAttached, signal: &QQuickAccessibleAttached::decreaseAction, receiver: this, slot: &QQuickScrollBar::decrease);
745 }
746 } else {
747 if (QQuickAccessibleAttached *accessibleAttached = QQuickControlPrivate::accessibleAttached(object: this)) {
748 disconnect(sender: accessibleAttached, signal: &QQuickAccessibleAttached::increaseAction, receiver: this, slot: &QQuickScrollBar::increase);
749 disconnect(sender: accessibleAttached, signal: &QQuickAccessibleAttached::decreaseAction, receiver: this, slot: &QQuickScrollBar::decrease);
750 }
751 }
752}
753
754QAccessible::Role QQuickScrollBar::accessibleRole() const
755{
756 return QAccessible::ScrollBar;
757}
758#endif
759
760void QQuickScrollBarAttachedPrivate::setFlickable(QQuickFlickable *item)
761{
762 if (flickable) {
763 // NOTE: Use removeItemChangeListener(Geometry) instead of updateOrRemoveGeometryChangeListener(Size).
764 // The latter doesn't remove the listener but only resets its types. Thus, it leaves behind a dangling
765 // pointer on destruction.
766 QQuickItemPrivate::get(item: flickable)->removeItemChangeListener(this, types: QQuickItemPrivate::Geometry);
767 if (horizontal)
768 cleanupHorizontal();
769 if (vertical)
770 cleanupVertical();
771 }
772
773 flickable = item;
774
775 if (item) {
776 QQuickItemPrivate::get(item)->updateOrAddGeometryChangeListener(listener: this, types: QQuickGeometryChange::Size);
777 if (horizontal)
778 initHorizontal();
779 if (vertical)
780 initVertical();
781 }
782}
783
784void QQuickScrollBarAttachedPrivate::initHorizontal()
785{
786 Q_ASSERT(flickable && horizontal);
787
788 connect(sender: flickable, signal: &QQuickFlickable::movingHorizontallyChanged, receiverPrivate: this, slot: &QQuickScrollBarAttachedPrivate::activateHorizontal);
789
790 // TODO: export QQuickFlickableVisibleArea
791 QObject *area = flickable->property(name: "visibleArea").value<QObject *>();
792 QObject::connect(sender: area, SIGNAL(widthRatioChanged(qreal)), receiver: horizontal, SLOT(setSize(qreal)));
793 QObject::connect(sender: area, SIGNAL(xPositionChanged(qreal)), receiver: horizontal, SLOT(setPosition(qreal)));
794
795 // ensure that the ScrollBar is stacked above the Flickable in a ScrollView
796 QQuickItem *parent = horizontal->parentItem();
797 if (parent && parent == flickable->parentItem())
798 horizontal->stackAfter(flickable);
799
800 layoutHorizontal();
801 horizontal->setSize(area->property(name: "widthRatio").toReal());
802 horizontal->setPosition(area->property(name: "xPosition").toReal());
803}
804
805void QQuickScrollBarAttachedPrivate::initVertical()
806{
807 Q_ASSERT(flickable && vertical);
808
809 connect(sender: flickable, signal: &QQuickFlickable::movingVerticallyChanged, receiverPrivate: this, slot: &QQuickScrollBarAttachedPrivate::activateVertical);
810
811 // TODO: export QQuickFlickableVisibleArea
812 QObject *area = flickable->property(name: "visibleArea").value<QObject *>();
813 QObject::connect(sender: area, SIGNAL(heightRatioChanged(qreal)), receiver: vertical, SLOT(setSize(qreal)));
814 QObject::connect(sender: area, SIGNAL(yPositionChanged(qreal)), receiver: vertical, SLOT(setPosition(qreal)));
815
816 // ensure that the ScrollBar is stacked above the Flickable in a ScrollView
817 QQuickItem *parent = vertical->parentItem();
818 if (parent && parent == flickable->parentItem())
819 vertical->stackAfter(flickable);
820
821 layoutVertical();
822 vertical->setSize(area->property(name: "heightRatio").toReal());
823 vertical->setPosition(area->property(name: "yPosition").toReal());
824}
825
826void QQuickScrollBarAttachedPrivate::cleanupHorizontal()
827{
828 Q_ASSERT(flickable && horizontal);
829
830 QQuickControlPrivate::hideOldItem(item: horizontal);
831 // ScrollBar.qml has a binding to visible and ScrollView.qml has a binding to parent.
832 // If we just set visible to false and parent to null, these bindings will overwrite
833 // them upon component completion as part of the binding evaluation.
834 // That's why we remove the binding completely.
835 const QQmlProperty visibleProperty(horizontal, QStringLiteral("visible"));
836 const QQmlProperty parentProperty(horizontal, QStringLiteral("parent"));
837 QQmlPropertyPrivate::removeBinding(that: visibleProperty);
838 QQmlPropertyPrivate::removeBinding(that: parentProperty);
839
840 disconnect(sender: flickable, signal: &QQuickFlickable::movingHorizontallyChanged, receiverPrivate: this, slot: &QQuickScrollBarAttachedPrivate::activateHorizontal);
841
842 // TODO: export QQuickFlickableVisibleArea
843 QObject *area = flickable->property(name: "visibleArea").value<QObject *>();
844 QObject::disconnect(sender: area, SIGNAL(widthRatioChanged(qreal)), receiver: horizontal, SLOT(setSize(qreal)));
845 QObject::disconnect(sender: area, SIGNAL(xPositionChanged(qreal)), receiver: horizontal, SLOT(setPosition(qreal)));
846}
847
848void QQuickScrollBarAttachedPrivate::cleanupVertical()
849{
850 Q_ASSERT(flickable && vertical);
851
852 QQuickControlPrivate::hideOldItem(item: vertical);
853 const QQmlProperty visibleProperty(vertical, QStringLiteral("visible"));
854 const QQmlProperty parentProperty(vertical, QStringLiteral("parent"));
855 QQmlPropertyPrivate::removeBinding(that: visibleProperty);
856 QQmlPropertyPrivate::removeBinding(that: parentProperty);
857
858 disconnect(sender: flickable, signal: &QQuickFlickable::movingVerticallyChanged, receiverPrivate: this, slot: &QQuickScrollBarAttachedPrivate::activateVertical);
859
860 // TODO: export QQuickFlickableVisibleArea
861 QObject *area = flickable->property(name: "visibleArea").value<QObject *>();
862 QObject::disconnect(sender: area, SIGNAL(heightRatioChanged(qreal)), receiver: vertical, SLOT(setSize(qreal)));
863 QObject::disconnect(sender: area, SIGNAL(yPositionChanged(qreal)), receiver: vertical, SLOT(setPosition(qreal)));
864}
865
866void QQuickScrollBarAttachedPrivate::activateHorizontal()
867{
868 QQuickScrollBarPrivate *p = QQuickScrollBarPrivate::get(bar: horizontal);
869 p->moving = flickable->isMovingHorizontally();
870 p->updateActive();
871}
872
873void QQuickScrollBarAttachedPrivate::activateVertical()
874{
875 QQuickScrollBarPrivate *p = QQuickScrollBarPrivate::get(bar: vertical);
876 p->moving = flickable->isMovingVertically();
877 p->updateActive();
878}
879
880// TODO: QQuickFlickable::maxXYExtent()
881class QQuickFriendlyFlickable : public QQuickFlickable
882{
883 friend class QQuickScrollBarAttachedPrivate;
884};
885
886void QQuickScrollBarAttachedPrivate::scrollHorizontal()
887{
888 if (!flickable)
889 return;
890
891 QQuickFriendlyFlickable *f = reinterpret_cast<QQuickFriendlyFlickable *>(flickable);
892
893 const qreal viewwidth = f->width();
894 const qreal maxxextent = -f->maxXExtent() + f->minXExtent();
895 const qreal cx = horizontal->position() * (maxxextent + viewwidth) - f->minXExtent();
896
897 if (!qIsNaN(d: cx) && !qFuzzyCompare(p1: cx, p2: flickable->contentX()))
898 flickable->setContentX(cx);
899}
900
901void QQuickScrollBarAttachedPrivate::scrollVertical()
902{
903 if (!flickable)
904 return;
905
906 QQuickFriendlyFlickable *f = reinterpret_cast<QQuickFriendlyFlickable *>(flickable);
907
908 const qreal viewheight = f->height();
909 const qreal maxyextent = -f->maxYExtent() + f->minYExtent();
910 const qreal cy = vertical->position() * (maxyextent + viewheight) - f->minYExtent();
911
912 if (!qIsNaN(d: cy) && !qFuzzyCompare(p1: cy, p2: flickable->contentY()))
913 flickable->setContentY(cy);
914}
915
916void QQuickScrollBarAttachedPrivate::mirrorVertical()
917{
918 layoutVertical(move: true);
919}
920
921void QQuickScrollBarAttachedPrivate::layoutHorizontal(bool move)
922{
923 Q_ASSERT(horizontal && flickable);
924 if (horizontal->parentItem() != flickable)
925 return;
926 horizontal->setWidth(flickable->width());
927 if (move)
928 horizontal->setY(flickable->height() - horizontal->height());
929}
930
931void QQuickScrollBarAttachedPrivate::layoutVertical(bool move)
932{
933 Q_ASSERT(vertical && flickable);
934 if (vertical->parentItem() != flickable)
935 return;
936 vertical->setHeight(flickable->height());
937 if (move)
938 vertical->setX(vertical->isMirrored() ? 0 : flickable->width() - vertical->width());
939}
940
941void QQuickScrollBarAttachedPrivate::itemGeometryChanged(QQuickItem *item, const QQuickGeometryChange change, const QRectF &diff)
942{
943 Q_UNUSED(item);
944 Q_UNUSED(change);
945 if (horizontal && horizontal->height() > 0) {
946#ifdef QT_QUICK_NEW_GEOMETRY_CHANGED_HANDLING // TODO: correct/rename diff to oldGeometry
947 bool move = qFuzzyIsNull(d: horizontal->y()) || qFuzzyCompare(p1: horizontal->y(), p2: diff.height() - horizontal->height());
948#else
949 bool move = qFuzzyIsNull(horizontal->y()) || qFuzzyCompare(horizontal->y(), item->height() - diff.height() - horizontal->height());
950#endif
951 if (flickable)
952 layoutHorizontal(move);
953 }
954 if (vertical && vertical->width() > 0) {
955#ifdef QT_QUICK_NEW_GEOMETRY_CHANGED_HANDLING // TODO: correct/rename diff to oldGeometry
956 bool move = qFuzzyIsNull(d: vertical->x()) || qFuzzyCompare(p1: vertical->x(), p2: diff.width() - vertical->width());
957#else
958 bool move = qFuzzyIsNull(vertical->x()) || qFuzzyCompare(vertical->x(), item->width() - diff.width() - vertical->width());
959#endif
960 if (flickable)
961 layoutVertical(move);
962 }
963}
964
965void QQuickScrollBarAttachedPrivate::itemImplicitWidthChanged(QQuickItem *item)
966{
967 if (item == vertical && flickable)
968 layoutVertical(move: true);
969}
970
971void QQuickScrollBarAttachedPrivate::itemImplicitHeightChanged(QQuickItem *item)
972{
973 if (item == horizontal && flickable)
974 layoutHorizontal(move: true);
975}
976
977void QQuickScrollBarAttachedPrivate::itemDestroyed(QQuickItem *item)
978{
979 if (item == horizontal)
980 horizontal = nullptr;
981 if (item == vertical)
982 vertical = nullptr;
983}
984
985QQuickScrollBarAttached::QQuickScrollBarAttached(QObject *parent)
986 : QObject(*(new QQuickScrollBarAttachedPrivate), parent)
987{
988 Q_D(QQuickScrollBarAttached);
989 d->setFlickable(qobject_cast<QQuickFlickable *>(object: parent));
990
991 if (parent && !d->flickable && !qobject_cast<QQuickScrollView *>(object: parent))
992 qmlWarning(me: parent) << "ScrollBar must be attached to a Flickable or ScrollView";
993}
994
995QQuickScrollBarAttached::~QQuickScrollBarAttached()
996{
997 Q_D(QQuickScrollBarAttached);
998 if (d->horizontal) {
999 QQuickItemPrivate::get(item: d->horizontal)->removeItemChangeListener(d, types: horizontalChangeTypes);
1000 d->horizontal = nullptr;
1001 }
1002 if (d->vertical) {
1003 QQuickItemPrivate::get(item: d->vertical)->removeItemChangeListener(d, types: verticalChangeTypes);
1004 d->vertical = nullptr;
1005 }
1006 d->setFlickable(nullptr);
1007}
1008
1009/*!
1010 \qmlattachedproperty ScrollBar QtQuick.Controls::ScrollBar::horizontal
1011
1012 This property attaches a horizontal scroll bar to a \l Flickable.
1013
1014 \code
1015 Flickable {
1016 contentWidth: 2000
1017 ScrollBar.horizontal: ScrollBar { }
1018 }
1019 \endcode
1020
1021 \sa {Attaching ScrollBar to a Flickable}
1022*/
1023QQuickScrollBar *QQuickScrollBarAttached::horizontal() const
1024{
1025 Q_D(const QQuickScrollBarAttached);
1026 return d->horizontal;
1027}
1028
1029void QQuickScrollBarAttached::setHorizontal(QQuickScrollBar *horizontal)
1030{
1031 Q_D(QQuickScrollBarAttached);
1032 if (d->horizontal == horizontal)
1033 return;
1034
1035 if (d->horizontal) {
1036 QQuickItemPrivate::get(item: d->horizontal)->removeItemChangeListener(d, types: horizontalChangeTypes);
1037 QObjectPrivate::disconnect(sender: d->horizontal, signal: &QQuickScrollBar::positionChanged, receiverPrivate: d, slot: &QQuickScrollBarAttachedPrivate::scrollHorizontal);
1038
1039 if (d->flickable)
1040 d->cleanupHorizontal();
1041 }
1042
1043 d->horizontal = horizontal;
1044
1045 if (horizontal) {
1046 if (!horizontal->parentItem())
1047 horizontal->setParentItem(qobject_cast<QQuickItem *>(object: parent()));
1048 horizontal->setOrientation(Qt::Horizontal);
1049
1050 QQuickItemPrivate::get(item: horizontal)->addItemChangeListener(listener: d, types: horizontalChangeTypes);
1051 QObjectPrivate::connect(sender: horizontal, signal: &QQuickScrollBar::positionChanged, receiverPrivate: d, slot: &QQuickScrollBarAttachedPrivate::scrollHorizontal);
1052
1053 if (d->flickable)
1054 d->initHorizontal();
1055 }
1056 emit horizontalChanged();
1057}
1058
1059/*!
1060 \qmlattachedproperty ScrollBar QtQuick.Controls::ScrollBar::vertical
1061
1062 This property attaches a vertical scroll bar to a \l Flickable.
1063
1064 \code
1065 Flickable {
1066 contentHeight: 2000
1067 ScrollBar.vertical: ScrollBar { }
1068 }
1069 \endcode
1070
1071 \sa {Attaching ScrollBar to a Flickable}
1072*/
1073QQuickScrollBar *QQuickScrollBarAttached::vertical() const
1074{
1075 Q_D(const QQuickScrollBarAttached);
1076 return d->vertical;
1077}
1078
1079void QQuickScrollBarAttached::setVertical(QQuickScrollBar *vertical)
1080{
1081 Q_D(QQuickScrollBarAttached);
1082 if (d->vertical == vertical)
1083 return;
1084
1085 if (d->vertical) {
1086 QQuickItemPrivate::get(item: d->vertical)->removeItemChangeListener(d, types: verticalChangeTypes);
1087 QObjectPrivate::disconnect(sender: d->vertical, signal: &QQuickScrollBar::mirroredChanged, receiverPrivate: d, slot: &QQuickScrollBarAttachedPrivate::mirrorVertical);
1088 QObjectPrivate::disconnect(sender: d->vertical, signal: &QQuickScrollBar::positionChanged, receiverPrivate: d, slot: &QQuickScrollBarAttachedPrivate::scrollVertical);
1089
1090 if (d->flickable)
1091 d->cleanupVertical();
1092 }
1093
1094 d->vertical = vertical;
1095
1096 if (vertical) {
1097 if (!vertical->parentItem())
1098 vertical->setParentItem(qobject_cast<QQuickItem *>(object: parent()));
1099 vertical->setOrientation(Qt::Vertical);
1100
1101 QQuickItemPrivate::get(item: vertical)->addItemChangeListener(listener: d, types: verticalChangeTypes);
1102 QObjectPrivate::connect(sender: vertical, signal: &QQuickScrollBar::mirroredChanged, receiverPrivate: d, slot: &QQuickScrollBarAttachedPrivate::mirrorVertical);
1103 QObjectPrivate::connect(sender: vertical, signal: &QQuickScrollBar::positionChanged, receiverPrivate: d, slot: &QQuickScrollBarAttachedPrivate::scrollVertical);
1104
1105 if (d->flickable)
1106 d->initVertical();
1107 }
1108 emit verticalChanged();
1109}
1110
1111QT_END_NAMESPACE
1112

source code of qtquickcontrols2/src/quicktemplates2/qquickscrollbar.cpp