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 | |
4 | #include "qquickscrollbar_p.h" |
5 | #include "qquickscrollbar_p_p.h" |
6 | #include "qquickscrollview_p.h" |
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 | |
14 | QT_BEGIN_NAMESPACE |
15 | |
16 | /*! |
17 | \qmltype ScrollBar |
18 | \inherits Control |
19 | //! \instantiates 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 | |
125 | static const QQuickItemPrivate::ChangeTypes QsbChangeTypes = QQuickItemPrivate::Geometry | QQuickItemPrivate::Destroyed; |
126 | static const QQuickItemPrivate::ChangeTypes QsbHorizontalChangeTypes = QsbChangeTypes | QQuickItemPrivate::ImplicitHeight; |
127 | static const QQuickItemPrivate::ChangeTypes QsbVerticalChangeTypes = QsbChangeTypes | QQuickItemPrivate::ImplicitWidth; |
128 | |
129 | QQuickScrollBarPrivate::VisualArea QQuickScrollBarPrivate::visualArea() const |
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>(a: 0.0, b: 1.0 - visualPos); |
137 | qreal visualSize = qMax<qreal>(a: minimumSize, |
138 | b: qMin<qreal>(a: qMax(a: size, b: minimumSize) + qMin<qreal>(a: 0, b: visualPos), |
139 | b: maximumSize)); |
140 | |
141 | visualPos = qMax<qreal>(a: 0,b: qMin<qreal>(a: visualPos,b: qMax<qreal>(a: 0, b: 1.0 - visualSize))); |
142 | |
143 | return VisualArea(visualPos, visualSize); |
144 | } |
145 | |
146 | qreal QQuickScrollBarPrivate::logicalPosition(qreal position) const |
147 | { |
148 | if (minimumSize > size && minimumSize != 1.0) |
149 | return position * (1.0 - size) / (1.0 - minimumSize); |
150 | return position; |
151 | } |
152 | |
153 | qreal QQuickScrollBarPrivate::snapPosition(qreal position) const |
154 | { |
155 | const qreal effectiveStep = stepSize * (1.0 - size); |
156 | if (qFuzzyIsNull(d: effectiveStep)) |
157 | return position; |
158 | |
159 | return qRound(d: position / effectiveStep) * effectiveStep; |
160 | } |
161 | |
162 | qreal QQuickScrollBarPrivate::positionAt(const QPointF &point) const |
163 | { |
164 | Q_Q(const QQuickScrollBar); |
165 | if (orientation == Qt::Horizontal) |
166 | return logicalPosition(position: point.x() - q->leftPadding()) / q->availableWidth(); |
167 | else |
168 | return logicalPosition(position: point.y() - q->topPadding()) / q->availableHeight(); |
169 | } |
170 | |
171 | void QQuickScrollBarPrivate::setInteractive(bool enabled) |
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 | |
199 | void QQuickScrollBarPrivate::updateActive() |
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 | |
210 | void QQuickScrollBarPrivate::resizeContent() |
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 | |
229 | void QQuickScrollBarPrivate::itemImplicitWidthChanged(QQuickItem *item) |
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 | |
243 | void QQuickScrollBarPrivate::itemImplicitHeightChanged(QQuickItem *item) |
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 | |
257 | bool 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(point: q->mapToItem(item: decreaseArrow, point: 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(point: q->mapToItem(item: increaseArrow, point: 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(a: size, b: logicalPosition(position: minimumSize)); |
281 | if (offset < 0 || offset > sz) |
282 | offset = sz / 2; |
283 | q->setPressed(true); |
284 | return true; |
285 | } |
286 | |
287 | bool 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>(a: 0.0, b: qMin<qreal>(a: positionAt(point) - offset, b: 1.0 - size)); |
303 | if (snapMode == QQuickScrollBar::SnapAlways) |
304 | pos = snapPosition(position: pos); |
305 | q->setPosition(pos); |
306 | return true; |
307 | } |
308 | |
309 | bool 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>(a: 0.0, b: qMin<qreal>(a: positionAt(point) - offset, b: 1.0 - size)); |
323 | if (snapMode != QQuickScrollBar::NoSnap) |
324 | pos = snapPosition(position: pos); |
325 | q->setPosition(pos); |
326 | offset = 0.0; |
327 | q->setPressed(false); |
328 | return true; |
329 | } |
330 | |
331 | void QQuickScrollBarPrivate::handleUngrab() |
332 | { |
333 | Q_Q(QQuickScrollBar); |
334 | QQuickControlPrivate::handleUngrab(); |
335 | offset = 0.0; |
336 | q->setPressed(false); |
337 | } |
338 | |
339 | void QQuickScrollBarPrivate::visualAreaChange(const VisualArea &newVisualArea, const VisualArea &oldVisualArea) |
340 | { |
341 | Q_Q(QQuickScrollBar); |
342 | if (!qFuzzyCompare(p1: newVisualArea.size, p2: oldVisualArea.size)) |
343 | emit q->visualSizeChanged(); |
344 | if (!qFuzzyCompare(p1: newVisualArea.position, p2: oldVisualArea.position)) |
345 | emit q->visualPositionChanged(); |
346 | } |
347 | |
348 | void 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(u: false); |
354 | if (!newHoverState.has_value()) { |
355 | if (QQuickItem *indicator = sbButton->indicator()) |
356 | hovered = indicator->contains(point: q->mapToItem(item: indicator, point: pos)); |
357 | } |
358 | sbButton->setHovered(hovered); |
359 | } |
360 | }; |
361 | updateHoverOnButton(q->decreaseVisual()); |
362 | updateHoverOnButton(q->increaseVisual()); |
363 | } |
364 | |
365 | QQuickScrollBar::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 | setKeepMouseGrab(true); |
372 | setAcceptedMouseButtons(Qt::LeftButton); |
373 | #if QT_CONFIG(quicktemplates2_multitouch) |
374 | setAcceptTouchEvents(true); |
375 | #endif |
376 | #if QT_CONFIG(cursor) |
377 | setCursor(Qt::ArrowCursor); |
378 | #endif |
379 | } |
380 | |
381 | QQuickScrollBarAttached *QQuickScrollBar::qmlAttachedProperties(QObject *object) |
382 | { |
383 | return new QQuickScrollBarAttached(object); |
384 | } |
385 | |
386 | /*! |
387 | \qmlproperty real QtQuick.Controls::ScrollBar::size |
388 | |
389 | This property holds the size of the scroll bar, scaled to \c {0.0 - 1.0}. |
390 | |
391 | \sa {Flickable::visibleArea.heightRatio}{Flickable::visibleArea} |
392 | |
393 | This property is automatically set when the scroll bar is |
394 | \l {Attaching ScrollBar to a Flickable}{attached to a flickable}. |
395 | |
396 | \sa minimumSize, visualSize |
397 | */ |
398 | qreal QQuickScrollBar::size() const |
399 | { |
400 | Q_D(const QQuickScrollBar); |
401 | return d->size; |
402 | } |
403 | |
404 | void QQuickScrollBar::setSize(qreal size) |
405 | { |
406 | Q_D(QQuickScrollBar); |
407 | if (!qt_is_finite(d: size)) |
408 | return; |
409 | size = qBound(min: 0.0, val: size, max: 1.0); |
410 | if (qFuzzyCompare(p1: d->size, p2: size)) |
411 | return; |
412 | |
413 | const auto oldVisualArea = d->visualArea(); |
414 | d->size = size; |
415 | if (d->size + d->position > 1.0) { |
416 | d->setPosition(position: 1.0 - d->size, notifyVisualChange: false); |
417 | } |
418 | |
419 | if (isComponentComplete()) |
420 | d->resizeContent(); |
421 | emit sizeChanged(); |
422 | d->visualAreaChange(newVisualArea: d->visualArea(), oldVisualArea); |
423 | } |
424 | |
425 | /*! |
426 | \qmlproperty real QtQuick.Controls::ScrollBar::position |
427 | |
428 | This property holds the position of the scroll bar, scaled to \c {0.0 - 1.0}. |
429 | |
430 | The largest valid scrollbar position is \c {(1.0 - size)}. This gives |
431 | correct behavior for the most used case where moving the scrollbar |
432 | to the end will put the end of the document at the lower end of the |
433 | visible area of the connected Flickable. |
434 | |
435 | \sa {Flickable::visibleArea.yPosition}{Flickable::visibleArea} |
436 | |
437 | This property is automatically set when the scroll bar is |
438 | \l {Attaching ScrollBar to a Flickable}{attached to a flickable}. |
439 | |
440 | \sa visualPosition |
441 | */ |
442 | qreal QQuickScrollBar::position() const |
443 | { |
444 | Q_D(const QQuickScrollBar); |
445 | return d->position; |
446 | } |
447 | |
448 | void QQuickScrollBar::setPosition(qreal position) |
449 | { |
450 | Q_D(QQuickScrollBar); |
451 | d->setPosition(position); |
452 | } |
453 | |
454 | void QQuickScrollBarPrivate::setPosition(qreal newPosition, bool notifyVisualChange) |
455 | { |
456 | Q_Q(QQuickScrollBar); |
457 | if (!qt_is_finite(d: newPosition) || qFuzzyCompare(p1: position, p2: newPosition)) |
458 | return; |
459 | |
460 | auto oldVisualArea = visualArea(); |
461 | position = newPosition; |
462 | if (q->isComponentComplete()) |
463 | resizeContent(); |
464 | emit q->positionChanged(); |
465 | if (notifyVisualChange) |
466 | visualAreaChange(newVisualArea: visualArea(), oldVisualArea); |
467 | } |
468 | |
469 | /*! |
470 | \qmlproperty real QtQuick.Controls::ScrollBar::stepSize |
471 | |
472 | This property holds the step size. The default value is \c 0.0. |
473 | |
474 | \sa snapMode, increase(), decrease() |
475 | */ |
476 | qreal QQuickScrollBar::stepSize() const |
477 | { |
478 | Q_D(const QQuickScrollBar); |
479 | return d->stepSize; |
480 | } |
481 | |
482 | void QQuickScrollBar::setStepSize(qreal step) |
483 | { |
484 | Q_D(QQuickScrollBar); |
485 | if (!qt_is_finite(d: step) || qFuzzyCompare(p1: d->stepSize, p2: step)) |
486 | return; |
487 | |
488 | d->stepSize = step; |
489 | emit stepSizeChanged(); |
490 | } |
491 | |
492 | /*! |
493 | \qmlproperty bool QtQuick.Controls::ScrollBar::active |
494 | |
495 | This property holds whether the scroll bar is active, i.e. when it's \l pressed |
496 | or the attached Flickable is \l {Flickable::moving}{moving}. |
497 | |
498 | It is possible to keep \l {Binding the Active State of Horizontal and Vertical Scroll Bars} |
499 | {both horizontal and vertical bars visible} while scrolling in either direction. |
500 | |
501 | This property is automatically set when the scroll bar is |
502 | \l {Attaching ScrollBar to a Flickable}{attached to a flickable}. |
503 | */ |
504 | bool QQuickScrollBar::isActive() const |
505 | { |
506 | Q_D(const QQuickScrollBar); |
507 | return d->active; |
508 | } |
509 | |
510 | void QQuickScrollBar::setActive(bool active) |
511 | { |
512 | Q_D(QQuickScrollBar); |
513 | if (d->active == active) |
514 | return; |
515 | |
516 | d->active = active; |
517 | emit activeChanged(); |
518 | } |
519 | |
520 | /*! |
521 | \qmlproperty bool QtQuick.Controls::ScrollBar::pressed |
522 | |
523 | This property holds whether the scroll bar is pressed. |
524 | */ |
525 | bool QQuickScrollBar::isPressed() const |
526 | { |
527 | Q_D(const QQuickScrollBar); |
528 | return d->pressed; |
529 | } |
530 | |
531 | void QQuickScrollBar::setPressed(bool pressed) |
532 | { |
533 | Q_D(QQuickScrollBar); |
534 | if (!pressed) { |
535 | if (QQuickIndicatorButton *button = decreaseVisual()) |
536 | button->setPressed(false); |
537 | if (QQuickIndicatorButton *button = increaseVisual()) |
538 | button->setPressed(false); |
539 | } |
540 | if (d->pressed == pressed) |
541 | return; |
542 | |
543 | d->pressed = pressed; |
544 | setAccessibleProperty(propertyName: "pressed" , value: pressed); |
545 | d->updateActive(); |
546 | emit pressedChanged(); |
547 | } |
548 | |
549 | /*! |
550 | \qmlproperty enumeration QtQuick.Controls::ScrollBar::orientation |
551 | |
552 | This property holds the orientation of the scroll bar. |
553 | |
554 | Possible values: |
555 | \value Qt.Horizontal Horizontal |
556 | \value Qt.Vertical Vertical (default) |
557 | |
558 | This property is automatically set when the scroll bar is |
559 | \l {Attaching ScrollBar to a Flickable}{attached to a flickable}. |
560 | |
561 | \sa horizontal, vertical |
562 | */ |
563 | Qt::Orientation QQuickScrollBar::orientation() const |
564 | { |
565 | Q_D(const QQuickScrollBar); |
566 | return d->orientation; |
567 | } |
568 | |
569 | void QQuickScrollBar::setOrientation(Qt::Orientation orientation) |
570 | { |
571 | Q_D(QQuickScrollBar); |
572 | if (d->orientation == orientation) |
573 | return; |
574 | |
575 | d->orientation = orientation; |
576 | if (isComponentComplete()) |
577 | d->resizeContent(); |
578 | emit orientationChanged(); |
579 | } |
580 | |
581 | /*! |
582 | \since QtQuick.Controls 2.2 (Qt 5.9) |
583 | \qmlproperty enumeration QtQuick.Controls::ScrollBar::snapMode |
584 | |
585 | This property holds the snap mode. |
586 | |
587 | Possible values: |
588 | \value ScrollBar.NoSnap The scrollbar does not snap (default). |
589 | \value ScrollBar.SnapAlways The scrollbar snaps while dragged. |
590 | \value ScrollBar.SnapOnRelease The scrollbar does not snap while being dragged, but only after released. |
591 | |
592 | In the following table, the various modes are illustrated with animations. |
593 | The movement and the \l stepSize (\c 0.25) are identical in each animation. |
594 | |
595 | \table |
596 | \header |
597 | \row \li \b Value \li \b Example |
598 | \row \li \c ScrollBar.NoSnap \li \image qtquickcontrols-scrollbar-nosnap.gif |
599 | \row \li \c ScrollBar.SnapAlways \li \image qtquickcontrols-scrollbar-snapalways.gif |
600 | \row \li \c ScrollBar.SnapOnRelease \li \image qtquickcontrols-scrollbar-snaponrelease.gif |
601 | \endtable |
602 | |
603 | \sa stepSize |
604 | */ |
605 | QQuickScrollBar::SnapMode QQuickScrollBar::snapMode() const |
606 | { |
607 | Q_D(const QQuickScrollBar); |
608 | return d->snapMode; |
609 | } |
610 | |
611 | void QQuickScrollBar::setSnapMode(SnapMode mode) |
612 | { |
613 | Q_D(QQuickScrollBar); |
614 | if (d->snapMode == mode) |
615 | return; |
616 | |
617 | d->snapMode = mode; |
618 | emit snapModeChanged(); |
619 | } |
620 | |
621 | /*! |
622 | \since QtQuick.Controls 2.2 (Qt 5.9) |
623 | \qmlproperty bool QtQuick.Controls::ScrollBar::interactive |
624 | |
625 | This property holds whether the scroll bar is interactive. The default value is \c true. |
626 | |
627 | A non-interactive scroll bar is visually and behaviorally similar to \l ScrollIndicator. |
628 | This property is useful for switching between typical mouse- and touch-orientated UIs |
629 | with interactive and non-interactive scroll bars, respectively. |
630 | */ |
631 | bool QQuickScrollBar::isInteractive() const |
632 | { |
633 | Q_D(const QQuickScrollBar); |
634 | return d->interactive; |
635 | } |
636 | |
637 | void QQuickScrollBar::setInteractive(bool interactive) |
638 | { |
639 | Q_D(QQuickScrollBar); |
640 | d->explicitInteractive = true; |
641 | d->setInteractive(interactive); |
642 | } |
643 | |
644 | void QQuickScrollBar::resetInteractive() |
645 | { |
646 | Q_D(QQuickScrollBar); |
647 | d->explicitInteractive = false; |
648 | d->setInteractive(true); |
649 | } |
650 | |
651 | /*! |
652 | \since QtQuick.Controls 2.2 (Qt 5.9) |
653 | \qmlproperty enumeration QtQuick.Controls::ScrollBar::policy |
654 | |
655 | This property holds the policy of the scroll bar. The default policy is \c ScrollBar.AsNeeded. |
656 | |
657 | Possible values: |
658 | \value ScrollBar.AsNeeded The scroll bar is only shown when the content is too large to fit. |
659 | \value ScrollBar.AlwaysOff The scroll bar is never shown. |
660 | \value ScrollBar.AlwaysOn The scroll bar is always shown. |
661 | |
662 | The following example keeps the vertical scroll bar always visible: |
663 | |
664 | \snippet qtquickcontrols-scrollbar-policy-alwayson.qml 1 |
665 | |
666 | Styles may use this property in combination with the \l active property |
667 | in order to implement transient scroll bars. Transient scroll bars are |
668 | hidden shortly after the last interaction event (hover or press). This |
669 | is typically done by animating the opacity of the scroll bar. To override |
670 | this behavior, set the policy to \c ScrollBar.AlwaysOn or |
671 | \c ScrollBar.AlwaysOff, depending on the size of the content compared to |
672 | its view. For example, for a vertical \l ListView: |
673 | |
674 | \snippet qtquickcontrols-scrollbar-policy-alwayson-when-needed.qml 1 |
675 | */ |
676 | QQuickScrollBar::Policy QQuickScrollBar::policy() const |
677 | { |
678 | Q_D(const QQuickScrollBar); |
679 | return d->policy; |
680 | } |
681 | |
682 | void QQuickScrollBar::setPolicy(Policy policy) |
683 | { |
684 | Q_D(QQuickScrollBar); |
685 | if (d->policy == policy) |
686 | return; |
687 | |
688 | d->policy = policy; |
689 | emit policyChanged(); |
690 | } |
691 | |
692 | /*! |
693 | \since QtQuick.Controls 2.3 (Qt 5.10) |
694 | \qmlproperty bool QtQuick.Controls::ScrollBar::horizontal |
695 | \readonly |
696 | |
697 | This property holds whether the scroll bar is horizontal. |
698 | |
699 | \sa orientation |
700 | */ |
701 | bool QQuickScrollBar::isHorizontal() const |
702 | { |
703 | Q_D(const QQuickScrollBar); |
704 | return d->orientation == Qt::Horizontal; |
705 | } |
706 | |
707 | /*! |
708 | \since QtQuick.Controls 2.3 (Qt 5.10) |
709 | \qmlproperty bool QtQuick.Controls::ScrollBar::vertical |
710 | \readonly |
711 | |
712 | This property holds whether the scroll bar is vertical. |
713 | |
714 | \sa orientation |
715 | */ |
716 | bool QQuickScrollBar::isVertical() const |
717 | { |
718 | Q_D(const QQuickScrollBar); |
719 | return d->orientation == Qt::Vertical; |
720 | } |
721 | |
722 | /*! |
723 | \since QtQuick.Controls 2.4 (Qt 5.11) |
724 | \qmlproperty real QtQuick.Controls::ScrollBar::minimumSize |
725 | |
726 | This property holds the minimum size of the scroll bar, scaled to \c {0.0 - 1.0}. |
727 | |
728 | \sa size, visualSize, visualPosition |
729 | */ |
730 | qreal QQuickScrollBar::minimumSize() const |
731 | { |
732 | Q_D(const QQuickScrollBar); |
733 | return d->minimumSize; |
734 | } |
735 | |
736 | void QQuickScrollBar::setMinimumSize(qreal minimumSize) |
737 | { |
738 | Q_D(QQuickScrollBar); |
739 | if (!qt_is_finite(d: minimumSize) || qFuzzyCompare(p1: d->minimumSize, p2: minimumSize)) |
740 | return; |
741 | |
742 | auto oldVisualArea = d->visualArea(); |
743 | d->minimumSize = qBound(min: 0.0, val: minimumSize, max: 1.0); |
744 | if (isComponentComplete()) |
745 | d->resizeContent(); |
746 | emit minimumSizeChanged(); |
747 | d->visualAreaChange(newVisualArea: d->visualArea(), oldVisualArea); |
748 | } |
749 | |
750 | /*! |
751 | \since QtQuick.Controls 2.4 (Qt 5.11) |
752 | \qmlproperty real QtQuick.Controls::ScrollBar::visualSize |
753 | |
754 | This property holds the effective visual size of the scroll bar, |
755 | which may be limited by the \l {minimumSize}{minimum size}. |
756 | |
757 | \sa size, minimumSize |
758 | */ |
759 | qreal QQuickScrollBar::visualSize() const |
760 | { |
761 | Q_D(const QQuickScrollBar); |
762 | return d->visualArea().size; |
763 | } |
764 | |
765 | /*! |
766 | \since QtQuick.Controls 2.4 (Qt 5.11) |
767 | \qmlproperty real QtQuick.Controls::ScrollBar::visualPosition |
768 | |
769 | This property holds the effective visual position of the scroll bar, |
770 | which may be limited by the \l {minimumSize}{minimum size}. |
771 | |
772 | \sa position, minimumSize |
773 | */ |
774 | qreal QQuickScrollBar::visualPosition() const |
775 | { |
776 | Q_D(const QQuickScrollBar); |
777 | return d->visualArea().position; |
778 | } |
779 | |
780 | QQuickIndicatorButton *QQuickScrollBar::decreaseVisual() |
781 | { |
782 | Q_D(QQuickScrollBar); |
783 | return d->decreaseVisual; |
784 | } |
785 | |
786 | QQuickIndicatorButton *QQuickScrollBar::increaseVisual() |
787 | { |
788 | Q_D(QQuickScrollBar); |
789 | return d->increaseVisual; |
790 | } |
791 | |
792 | /*! |
793 | \qmlmethod void QtQuick.Controls::ScrollBar::increase() |
794 | |
795 | Increases the position by \l stepSize or \c 0.1 if stepSize is \c 0.0. |
796 | |
797 | \sa stepSize |
798 | */ |
799 | void QQuickScrollBar::increase() |
800 | { |
801 | Q_D(QQuickScrollBar); |
802 | qreal step = qFuzzyIsNull(d: d->stepSize) ? 0.1 : d->stepSize; |
803 | bool wasActive = d->active; |
804 | setActive(true); |
805 | setPosition(qMin<qreal>(a: 1.0 - d->size, b: d->position + step)); |
806 | setActive(wasActive); |
807 | } |
808 | |
809 | /*! |
810 | \qmlmethod void QtQuick.Controls::ScrollBar::decrease() |
811 | |
812 | Decreases the position by \l stepSize or \c 0.1 if stepSize is \c 0.0. |
813 | |
814 | \sa stepSize |
815 | */ |
816 | void QQuickScrollBar::decrease() |
817 | { |
818 | Q_D(QQuickScrollBar); |
819 | qreal step = qFuzzyIsNull(d: d->stepSize) ? 0.1 : d->stepSize; |
820 | bool wasActive = d->active; |
821 | setActive(true); |
822 | setPosition(qMax<qreal>(a: 0.0, b: d->position - step)); |
823 | setActive(wasActive); |
824 | } |
825 | |
826 | void QQuickScrollBar::mousePressEvent(QMouseEvent *event) |
827 | { |
828 | Q_D(QQuickScrollBar); |
829 | QQuickControl::mousePressEvent(event); |
830 | d->handleMove(point: event->position(), timestamp: event->timestamp()); |
831 | } |
832 | |
833 | #if QT_CONFIG(quicktemplates2_hover) |
834 | void QQuickScrollBar::hoverChange() |
835 | { |
836 | Q_D(QQuickScrollBar); |
837 | d->updateActive(); |
838 | } |
839 | |
840 | void QQuickScrollBar::hoverEnterEvent(QHoverEvent *event) |
841 | { |
842 | Q_D(QQuickScrollBar); |
843 | QQuickControl::hoverEnterEvent(event); |
844 | d->updateHover(pos: event->position()); |
845 | event->ignore(); |
846 | } |
847 | |
848 | void QQuickScrollBar::hoverMoveEvent(QHoverEvent *event) |
849 | { |
850 | Q_D(QQuickScrollBar); |
851 | QQuickControl::hoverMoveEvent(event); |
852 | d->updateHover(pos: event->position()); |
853 | event->ignore(); |
854 | } |
855 | |
856 | void QQuickScrollBar::hoverLeaveEvent(QHoverEvent *event) |
857 | { |
858 | Q_D(QQuickScrollBar); |
859 | QQuickControl::hoverLeaveEvent(event); |
860 | |
861 | d->updateHover(pos: QPoint(), newHoverState: false); //position is not needed when we force it to unhover |
862 | event->ignore(); |
863 | } |
864 | #endif |
865 | |
866 | void QQuickScrollBar::classBegin() |
867 | { |
868 | Q_D(QQuickScrollBar); |
869 | QQuickControl::classBegin(); |
870 | |
871 | QQmlContext *context = qmlContext(this); |
872 | if (context) { |
873 | QQmlEngine::setContextForObject(d->decreaseVisual, context); |
874 | QQmlEngine::setContextForObject(d->increaseVisual, context); |
875 | } |
876 | } |
877 | |
878 | void QQuickScrollBar::componentComplete() |
879 | { |
880 | Q_D(QQuickScrollBar); |
881 | QQuickIndicatorButtonPrivate::get(button: d->decreaseVisual)->executeIndicator(complete: true); |
882 | QQuickIndicatorButtonPrivate::get(button: d->increaseVisual)->executeIndicator(complete: true); |
883 | |
884 | QQuickControl::componentComplete(); |
885 | } |
886 | |
887 | #if QT_CONFIG(accessibility) |
888 | void QQuickScrollBar::accessibilityActiveChanged(bool active) |
889 | { |
890 | QQuickControl::accessibilityActiveChanged(active); |
891 | |
892 | Q_D(QQuickScrollBar); |
893 | if (active) { |
894 | setAccessibleProperty(propertyName: "pressed" , value: d->pressed); |
895 | |
896 | if (QQuickAccessibleAttached *accessibleAttached = QQuickControlPrivate::accessibleAttached(object: this)) { |
897 | connect(sender: accessibleAttached, signal: &QQuickAccessibleAttached::increaseAction, context: this, slot: &QQuickScrollBar::increase); |
898 | connect(sender: accessibleAttached, signal: &QQuickAccessibleAttached::decreaseAction, context: this, slot: &QQuickScrollBar::decrease); |
899 | } |
900 | } else { |
901 | if (QQuickAccessibleAttached *accessibleAttached = QQuickControlPrivate::accessibleAttached(object: this)) { |
902 | disconnect(sender: accessibleAttached, signal: &QQuickAccessibleAttached::increaseAction, receiver: this, slot: &QQuickScrollBar::increase); |
903 | disconnect(sender: accessibleAttached, signal: &QQuickAccessibleAttached::decreaseAction, receiver: this, slot: &QQuickScrollBar::decrease); |
904 | } |
905 | } |
906 | } |
907 | |
908 | QAccessible::Role QQuickScrollBar::accessibleRole() const |
909 | { |
910 | return QAccessible::ScrollBar; |
911 | } |
912 | #endif |
913 | |
914 | void QQuickScrollBarAttachedPrivate::setFlickable(QQuickFlickable *item) |
915 | { |
916 | if (flickable) { |
917 | // NOTE: Use removeItemChangeListener(Geometry) instead of updateOrRemoveGeometryChangeListener(Size). |
918 | // The latter doesn't remove the listener but only resets its types. Thus, it leaves behind a dangling |
919 | // pointer on destruction. |
920 | QQuickItemPrivate::get(item: flickable)->removeItemChangeListener(this, types: QQuickItemPrivate::Geometry); |
921 | QQuickItemPrivate::get(item: flickable)->removeItemChangeListener(this, types: QQuickItemPrivate::Destroyed); |
922 | if (horizontal) |
923 | cleanupHorizontal(); |
924 | if (vertical) |
925 | cleanupVertical(); |
926 | } |
927 | |
928 | flickable = item; |
929 | |
930 | if (item) { |
931 | // Don't know how to combine these calls into one, and as long as they're separate calls, |
932 | // the remove* calls above need to be separate too, otherwise they will have no effect. |
933 | QQuickItemPrivate::get(item)->updateOrAddGeometryChangeListener(listener: this, types: QQuickGeometryChange::Size); |
934 | QQuickItemPrivate::get(item)->updateOrAddItemChangeListener(listener: this, types: QQuickItemPrivate::Destroyed); |
935 | if (horizontal) |
936 | initHorizontal(); |
937 | if (vertical) |
938 | initVertical(); |
939 | } |
940 | } |
941 | |
942 | void QQuickScrollBarAttachedPrivate::initHorizontal() |
943 | { |
944 | Q_ASSERT(flickable && horizontal); |
945 | |
946 | connect(sender: flickable, signal: &QQuickFlickable::movingHorizontallyChanged, receiverPrivate: this, slot: &QQuickScrollBarAttachedPrivate::activateHorizontal); |
947 | |
948 | // TODO: export QQuickFlickableVisibleArea |
949 | QObject *area = flickable->property(name: "visibleArea" ).value<QObject *>(); |
950 | QObject::connect(sender: area, SIGNAL(widthRatioChanged(qreal)), receiver: horizontal, SLOT(setSize(qreal))); |
951 | QObject::connect(sender: area, SIGNAL(xPositionChanged(qreal)), receiver: horizontal, SLOT(setPosition(qreal))); |
952 | |
953 | // ensure that the ScrollBar is stacked above the Flickable in a ScrollView |
954 | QQuickItem *parent = horizontal->parentItem(); |
955 | if (parent && parent == flickable->parentItem()) |
956 | horizontal->stackAfter(flickable); |
957 | |
958 | // If a scroll bar was previously hidden (due to e.g. setting a new contentItem |
959 | // on a ScrollView), we need to make sure that we un-hide it. |
960 | if (auto control = qobject_cast<QQuickControl*>(object: q_func()->parent())) { |
961 | const auto visibility = horizontal->policy() != QQuickScrollBar::AlwaysOff |
962 | ? QQuickControlPrivate::UnhideVisibility::Show : QQuickControlPrivate::UnhideVisibility::Hide; |
963 | QQuickControlPrivate::unhideOldItem(control, item: horizontal, visibility); |
964 | } |
965 | |
966 | layoutHorizontal(); |
967 | horizontal->setSize(area->property(name: "widthRatio" ).toReal()); |
968 | horizontal->setPosition(area->property(name: "xPosition" ).toReal()); |
969 | } |
970 | |
971 | void QQuickScrollBarAttachedPrivate::initVertical() |
972 | { |
973 | Q_ASSERT(flickable && vertical); |
974 | |
975 | connect(sender: flickable, signal: &QQuickFlickable::movingVerticallyChanged, receiverPrivate: this, slot: &QQuickScrollBarAttachedPrivate::activateVertical); |
976 | |
977 | // TODO: export QQuickFlickableVisibleArea |
978 | QObject *area = flickable->property(name: "visibleArea" ).value<QObject *>(); |
979 | QObject::connect(sender: area, SIGNAL(heightRatioChanged(qreal)), receiver: vertical, SLOT(setSize(qreal))); |
980 | QObject::connect(sender: area, SIGNAL(yPositionChanged(qreal)), receiver: vertical, SLOT(setPosition(qreal))); |
981 | |
982 | // ensure that the ScrollBar is stacked above the Flickable in a ScrollView |
983 | QQuickItem *parent = vertical->parentItem(); |
984 | if (parent && parent == flickable->parentItem()) |
985 | vertical->stackAfter(flickable); |
986 | |
987 | if (auto control = qobject_cast<QQuickControl*>(object: q_func()->parent())) { |
988 | const auto visibility = vertical->policy() != QQuickScrollBar::AlwaysOff |
989 | ? QQuickControlPrivate::UnhideVisibility::Show : QQuickControlPrivate::UnhideVisibility::Hide; |
990 | QQuickControlPrivate::unhideOldItem(control, item: vertical, visibility); |
991 | } |
992 | |
993 | layoutVertical(); |
994 | vertical->setSize(area->property(name: "heightRatio" ).toReal()); |
995 | vertical->setPosition(area->property(name: "yPosition" ).toReal()); |
996 | } |
997 | |
998 | void QQuickScrollBarAttachedPrivate::cleanupHorizontal() |
999 | { |
1000 | Q_ASSERT(flickable && horizontal); |
1001 | |
1002 | QQuickControlPrivate::hideOldItem(item: horizontal); |
1003 | // ScrollBar.qml has a binding to visible and ScrollView.qml has a binding to parent. |
1004 | // If we just set visible to false and parent to null, these bindings will overwrite |
1005 | // them upon component completion as part of the binding evaluation. |
1006 | // That's why we remove the binding completely. |
1007 | const QQmlProperty visibleProperty(horizontal, QStringLiteral("visible" )); |
1008 | const QQmlProperty parentProperty(horizontal, QStringLiteral("parent" )); |
1009 | QQmlPropertyPrivate::removeBinding(that: visibleProperty); |
1010 | QQmlPropertyPrivate::removeBinding(that: parentProperty); |
1011 | |
1012 | disconnect(sender: flickable, signal: &QQuickFlickable::movingHorizontallyChanged, receiverPrivate: this, slot: &QQuickScrollBarAttachedPrivate::activateHorizontal); |
1013 | |
1014 | // TODO: export QQuickFlickableVisibleArea |
1015 | QObject *area = flickable->property(name: "visibleArea" ).value<QObject *>(); |
1016 | QObject::disconnect(sender: area, SIGNAL(widthRatioChanged(qreal)), receiver: horizontal, SLOT(setSize(qreal))); |
1017 | QObject::disconnect(sender: area, SIGNAL(xPositionChanged(qreal)), receiver: horizontal, SLOT(setPosition(qreal))); |
1018 | } |
1019 | |
1020 | void QQuickScrollBarAttachedPrivate::cleanupVertical() |
1021 | { |
1022 | Q_ASSERT(flickable && vertical); |
1023 | |
1024 | QQuickControlPrivate::hideOldItem(item: vertical); |
1025 | const QQmlProperty visibleProperty(vertical, QStringLiteral("visible" )); |
1026 | const QQmlProperty parentProperty(vertical, QStringLiteral("parent" )); |
1027 | QQmlPropertyPrivate::removeBinding(that: visibleProperty); |
1028 | QQmlPropertyPrivate::removeBinding(that: parentProperty); |
1029 | |
1030 | disconnect(sender: flickable, signal: &QQuickFlickable::movingVerticallyChanged, receiverPrivate: this, slot: &QQuickScrollBarAttachedPrivate::activateVertical); |
1031 | |
1032 | // TODO: export QQuickFlickableVisibleArea |
1033 | QObject *area = flickable->property(name: "visibleArea" ).value<QObject *>(); |
1034 | QObject::disconnect(sender: area, SIGNAL(heightRatioChanged(qreal)), receiver: vertical, SLOT(setSize(qreal))); |
1035 | QObject::disconnect(sender: area, SIGNAL(yPositionChanged(qreal)), receiver: vertical, SLOT(setPosition(qreal))); |
1036 | } |
1037 | |
1038 | void QQuickScrollBarAttachedPrivate::activateHorizontal() |
1039 | { |
1040 | QQuickScrollBarPrivate *p = QQuickScrollBarPrivate::get(bar: horizontal); |
1041 | p->moving = flickable->isMovingHorizontally(); |
1042 | p->updateActive(); |
1043 | } |
1044 | |
1045 | void QQuickScrollBarAttachedPrivate::activateVertical() |
1046 | { |
1047 | QQuickScrollBarPrivate *p = QQuickScrollBarPrivate::get(bar: vertical); |
1048 | p->moving = flickable->isMovingVertically(); |
1049 | p->updateActive(); |
1050 | } |
1051 | |
1052 | // TODO: QQuickFlickable::maxXYExtent() |
1053 | class QQuickFriendlyFlickable : public QQuickFlickable |
1054 | { |
1055 | friend class QQuickScrollBarAttachedPrivate; |
1056 | }; |
1057 | |
1058 | void QQuickScrollBarAttachedPrivate::scrollHorizontal() |
1059 | { |
1060 | if (!flickable) |
1061 | return; |
1062 | |
1063 | QQuickFriendlyFlickable *f = reinterpret_cast<QQuickFriendlyFlickable *>(flickable); |
1064 | |
1065 | const qreal viewwidth = f->width(); |
1066 | const qreal maxxextent = -f->maxXExtent() + f->minXExtent(); |
1067 | const qreal cx = horizontal->position() * (maxxextent + viewwidth) - f->minXExtent(); |
1068 | |
1069 | if (!qIsNaN(d: cx) && !qFuzzyCompare(p1: cx, p2: flickable->contentX())) |
1070 | flickable->setContentX(cx); |
1071 | } |
1072 | |
1073 | void QQuickScrollBarAttachedPrivate::scrollVertical() |
1074 | { |
1075 | if (!flickable) |
1076 | return; |
1077 | |
1078 | QQuickFriendlyFlickable *f = reinterpret_cast<QQuickFriendlyFlickable *>(flickable); |
1079 | |
1080 | const qreal viewheight = f->height(); |
1081 | const qreal maxyextent = -f->maxYExtent() + f->minYExtent(); |
1082 | const qreal cy = vertical->position() * (maxyextent + viewheight) - f->minYExtent(); |
1083 | |
1084 | if (!qIsNaN(d: cy) && !qFuzzyCompare(p1: cy, p2: flickable->contentY())) |
1085 | flickable->setContentY(cy); |
1086 | } |
1087 | |
1088 | void QQuickScrollBarAttachedPrivate::mirrorVertical() |
1089 | { |
1090 | layoutVertical(move: true); |
1091 | } |
1092 | |
1093 | void QQuickScrollBarAttachedPrivate::layoutHorizontal(bool move) |
1094 | { |
1095 | Q_ASSERT(horizontal && flickable); |
1096 | if (horizontal->parentItem() != flickable) |
1097 | return; |
1098 | horizontal->setWidth(flickable->width()); |
1099 | if (move) |
1100 | horizontal->setY(flickable->height() - horizontal->height()); |
1101 | } |
1102 | |
1103 | void QQuickScrollBarAttachedPrivate::layoutVertical(bool move) |
1104 | { |
1105 | Q_ASSERT(vertical && flickable); |
1106 | if (vertical->parentItem() != flickable) |
1107 | return; |
1108 | vertical->setHeight(flickable->height()); |
1109 | if (move) |
1110 | vertical->setX(vertical->isMirrored() ? 0 : flickable->width() - vertical->width()); |
1111 | } |
1112 | |
1113 | void QQuickScrollBarAttachedPrivate::itemGeometryChanged(QQuickItem *item, const QQuickGeometryChange change, const QRectF &diff) |
1114 | { |
1115 | Q_UNUSED(item); |
1116 | Q_UNUSED(change); |
1117 | if (horizontal && horizontal->height() > 0) { |
1118 | #ifdef QT_QUICK_NEW_GEOMETRY_CHANGED_HANDLING // TODO: correct/rename diff to oldGeometry |
1119 | bool move = qFuzzyIsNull(d: horizontal->y()) || qFuzzyCompare(p1: horizontal->y(), p2: diff.height() - horizontal->height()); |
1120 | #else |
1121 | bool move = qFuzzyIsNull(horizontal->y()) || qFuzzyCompare(horizontal->y(), item->height() - diff.height() - horizontal->height()); |
1122 | #endif |
1123 | if (flickable) |
1124 | layoutHorizontal(move); |
1125 | } |
1126 | if (vertical && vertical->width() > 0) { |
1127 | #ifdef QT_QUICK_NEW_GEOMETRY_CHANGED_HANDLING // TODO: correct/rename diff to oldGeometry |
1128 | bool move = qFuzzyIsNull(d: vertical->x()) || qFuzzyCompare(p1: vertical->x(), p2: diff.width() - vertical->width()); |
1129 | #else |
1130 | bool move = qFuzzyIsNull(vertical->x()) || qFuzzyCompare(vertical->x(), item->width() - diff.width() - vertical->width()); |
1131 | #endif |
1132 | if (flickable) |
1133 | layoutVertical(move); |
1134 | } |
1135 | } |
1136 | |
1137 | void QQuickScrollBarAttachedPrivate::itemImplicitWidthChanged(QQuickItem *item) |
1138 | { |
1139 | if (item == vertical && flickable) |
1140 | layoutVertical(move: true); |
1141 | } |
1142 | |
1143 | void QQuickScrollBarAttachedPrivate::itemImplicitHeightChanged(QQuickItem *item) |
1144 | { |
1145 | if (item == horizontal && flickable) |
1146 | layoutHorizontal(move: true); |
1147 | } |
1148 | |
1149 | void QQuickScrollBarAttachedPrivate::itemDestroyed(QQuickItem *item) |
1150 | { |
1151 | if (item == flickable) |
1152 | flickable = nullptr; |
1153 | if (item == horizontal) |
1154 | horizontal = nullptr; |
1155 | if (item == vertical) |
1156 | vertical = nullptr; |
1157 | } |
1158 | |
1159 | QQuickScrollBarAttached::QQuickScrollBarAttached(QObject *parent) |
1160 | : QObject(*(new QQuickScrollBarAttachedPrivate), parent) |
1161 | { |
1162 | Q_D(QQuickScrollBarAttached); |
1163 | d->setFlickable(qobject_cast<QQuickFlickable *>(object: parent)); |
1164 | |
1165 | if (parent && !d->flickable && !qobject_cast<QQuickScrollView *>(object: parent)) |
1166 | qmlWarning(me: parent) << "ScrollBar must be attached to a Flickable or ScrollView" ; |
1167 | } |
1168 | |
1169 | QQuickScrollBarAttached::~QQuickScrollBarAttached() |
1170 | { |
1171 | Q_D(QQuickScrollBarAttached); |
1172 | if (d->horizontal) { |
1173 | QQuickItemPrivate::get(item: d->horizontal)->removeItemChangeListener(d, types: QsbHorizontalChangeTypes); |
1174 | d->horizontal = nullptr; |
1175 | } |
1176 | if (d->vertical) { |
1177 | QQuickItemPrivate::get(item: d->vertical)->removeItemChangeListener(d, types: QsbVerticalChangeTypes); |
1178 | d->vertical = nullptr; |
1179 | } |
1180 | d->setFlickable(nullptr); |
1181 | } |
1182 | |
1183 | /*! |
1184 | \qmlattachedproperty ScrollBar QtQuick.Controls::ScrollBar::horizontal |
1185 | |
1186 | This property attaches a horizontal scroll bar to a \l Flickable. |
1187 | |
1188 | \code |
1189 | Flickable { |
1190 | contentWidth: 2000 |
1191 | ScrollBar.horizontal: ScrollBar { } |
1192 | } |
1193 | \endcode |
1194 | |
1195 | \sa {Attaching ScrollBar to a Flickable} |
1196 | */ |
1197 | QQuickScrollBar *QQuickScrollBarAttached::horizontal() const |
1198 | { |
1199 | Q_D(const QQuickScrollBarAttached); |
1200 | return d->horizontal; |
1201 | } |
1202 | |
1203 | void QQuickScrollBarAttached::setHorizontal(QQuickScrollBar *horizontal) |
1204 | { |
1205 | Q_D(QQuickScrollBarAttached); |
1206 | if (d->horizontal == horizontal) |
1207 | return; |
1208 | |
1209 | if (d->horizontal) { |
1210 | QQuickItemPrivate::get(item: d->horizontal)->removeItemChangeListener(d, types: QsbHorizontalChangeTypes); |
1211 | QObjectPrivate::disconnect(sender: d->horizontal, signal: &QQuickScrollBar::positionChanged, receiverPrivate: d, slot: &QQuickScrollBarAttachedPrivate::scrollHorizontal); |
1212 | |
1213 | if (d->flickable) |
1214 | d->cleanupHorizontal(); |
1215 | } |
1216 | |
1217 | d->horizontal = horizontal; |
1218 | |
1219 | if (horizontal) { |
1220 | if (!horizontal->parentItem()) |
1221 | horizontal->setParentItem(qobject_cast<QQuickItem *>(o: parent())); |
1222 | horizontal->setOrientation(Qt::Horizontal); |
1223 | |
1224 | QQuickItemPrivate::get(item: horizontal)->addItemChangeListener(listener: d, types: QsbHorizontalChangeTypes); |
1225 | QObjectPrivate::connect(sender: horizontal, signal: &QQuickScrollBar::positionChanged, receiverPrivate: d, slot: &QQuickScrollBarAttachedPrivate::scrollHorizontal); |
1226 | |
1227 | if (d->flickable) |
1228 | d->initHorizontal(); |
1229 | } |
1230 | emit horizontalChanged(); |
1231 | } |
1232 | |
1233 | /*! |
1234 | \qmlattachedproperty ScrollBar QtQuick.Controls::ScrollBar::vertical |
1235 | |
1236 | This property attaches a vertical scroll bar to a \l Flickable. |
1237 | |
1238 | \code |
1239 | Flickable { |
1240 | contentHeight: 2000 |
1241 | ScrollBar.vertical: ScrollBar { } |
1242 | } |
1243 | \endcode |
1244 | |
1245 | \sa {Attaching ScrollBar to a Flickable} |
1246 | */ |
1247 | QQuickScrollBar *QQuickScrollBarAttached::vertical() const |
1248 | { |
1249 | Q_D(const QQuickScrollBarAttached); |
1250 | return d->vertical; |
1251 | } |
1252 | |
1253 | void QQuickScrollBarAttached::setVertical(QQuickScrollBar *vertical) |
1254 | { |
1255 | Q_D(QQuickScrollBarAttached); |
1256 | if (d->vertical == vertical) |
1257 | return; |
1258 | |
1259 | if (d->vertical) { |
1260 | QQuickItemPrivate::get(item: d->vertical)->removeItemChangeListener(d, types: QsbVerticalChangeTypes); |
1261 | QObjectPrivate::disconnect(sender: d->vertical, signal: &QQuickScrollBar::mirroredChanged, receiverPrivate: d, slot: &QQuickScrollBarAttachedPrivate::mirrorVertical); |
1262 | QObjectPrivate::disconnect(sender: d->vertical, signal: &QQuickScrollBar::positionChanged, receiverPrivate: d, slot: &QQuickScrollBarAttachedPrivate::scrollVertical); |
1263 | |
1264 | if (d->flickable) |
1265 | d->cleanupVertical(); |
1266 | } |
1267 | |
1268 | d->vertical = vertical; |
1269 | |
1270 | if (vertical) { |
1271 | if (!vertical->parentItem()) |
1272 | vertical->setParentItem(qobject_cast<QQuickItem *>(o: parent())); |
1273 | vertical->setOrientation(Qt::Vertical); |
1274 | |
1275 | QQuickItemPrivate::get(item: vertical)->addItemChangeListener(listener: d, types: QsbVerticalChangeTypes); |
1276 | QObjectPrivate::connect(sender: vertical, signal: &QQuickScrollBar::mirroredChanged, receiverPrivate: d, slot: &QQuickScrollBarAttachedPrivate::mirrorVertical); |
1277 | QObjectPrivate::connect(sender: vertical, signal: &QQuickScrollBar::positionChanged, receiverPrivate: d, slot: &QQuickScrollBarAttachedPrivate::scrollVertical); |
1278 | |
1279 | if (d->flickable) |
1280 | d->initVertical(); |
1281 | } |
1282 | emit verticalChanged(); |
1283 | } |
1284 | |
1285 | QT_END_NAMESPACE |
1286 | |
1287 | #include "moc_qquickscrollbar_p.cpp" |
1288 | |