Kirigami2

ScrollablePage.qml
1/*
2 * SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
3 *
4 * SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6
7import QtQuick
8import QtQml
9import QtQuick.Controls as QQC2
10import org.kde.kirigami as Kirigami
11import org.kde.kirigami.templates as KT
12import "private"
13
14
15// TODO KF6: undo many workarounds to make existing code work?
16
17/**
18 * @brief ScrollablePage is a Page that holds scrollable content, such as a ListView.
19 *
20 * Scrolling and scrolling indicators will be automatically managed.
21 *
22 * Example usage:
23 * @code
24 * ScrollablePage {
25 * id: root
26 * // The page will automatically be scrollable
27 * Rectangle {
28 * width: root.width
29 * height: 99999
30 * }
31 * }
32 * @endcode
33 *
34 * @warning Do not put a ScrollView inside of a ScrollablePage; children of a ScrollablePage are already inside a ScrollView.
35 *
36 * Another behavior added by this class is a "scroll down to refresh" behavior
37 * It also can give the contents of the flickable to have more top margins in order
38 * to make possible to scroll down the list to reach it with the thumb while using the
39 * phone with a single hand.
40 *
41 * Implementations should handle the refresh themselves as follows
42 *
43 * Example usage:
44 * @code
45 * Kirigami.ScrollablePage {
46 * id: view
47 * supportsRefreshing: true
48 * onRefreshingChanged: {
49 * if (refreshing) {
50 * myModel.refresh();
51 * }
52 * }
53 * ListView {
54 * // NOTE: MyModel doesn't come from the components,
55 * // it's purely an example on how it can be used together
56 * // some application logic that can update the list model
57 * // and signals when it's done.
58 * model: MyModel {
59 * onRefreshDone: view.refreshing = false;
60 * }
61 * delegate: ItemDelegate {}
62 * }
63 * }
64 * [...]
65 * @endcode
66 */
67Kirigami.Page {
68 id: root
69
70//BEGIN properties
71 /**
72 * @brief This property tells whether the list is asking for a refresh.
73 *
74 * This property will automatically be set to true when the user pulls the list down enough,
75 * which in return, shows a loading spinner. When this is set to true, it signals
76 * the application logic to start its refresh procedure.
77 *
78 * default: ``false``
79 *
80 * @note The application itself will have to set back this property to false when done.
81 */
82 property bool refreshing: false
83
84 /**
85 * @brief This property sets whether scrollable page supports "pull down to refresh" behaviour.
86 *
87 * default: ``false``
88 */
89 property bool supportsRefreshing: false
90
91 /**
92 * @brief This property holds the main Flickable item of this page.
93 * @deprecated here for compatibility; will be removed in KF6.
94 */
95 property Flickable flickable: Flickable {} // FIXME KF6: this empty flickable exists for compatibility reasons. some apps assume flickable exists right from the beginning but ScrollView internally assumes it does not
96 onFlickableChanged: scrollView.contentItem = flickable;
97
98 /**
99 * @brief This property sets the vertical scrollbar policy.
100 * @property Qt::ScrollBarPolicy verticalScrollBarPolicy
101 */
102 property int verticalScrollBarPolicy
103
104 /**
105 * @brief Set if the vertical scrollbar should be interactable.
106 * @property bool verticalScrollBarInteractive
107 */
108 property bool verticalScrollBarInteractive: true
109
110 /**
111 * @brief This property sets the horizontal scrollbar policy.
112 * @property Qt::ScrollBarPolicy horizontalScrollBarPolicy
113 */
114 property int horizontalScrollBarPolicy: QQC2.ScrollBar.AlwaysOff
115
116 /**
117 * @brief Set if the horizontal scrollbar should be interactable.
118 * @property bool horizontalScrollBarInteractive
119 */
120 property bool horizontalScrollBarInteractive: true
121
122 default property alias scrollablePageData: itemsParent.data
123 property alias scrollablePageChildren: itemsParent.children
124
125 /*
126 * @deprecated here for compatibility; will be removed in KF6.
127 */
128 property QtObject mainItem
129 onMainItemChanged: {
130 print("Warning: the mainItem property is deprecated");
131 scrollablePageData.push(mainItem);
132 }
134 /**
135 * @brief This property sets whether it is possible to navigate the items in a view that support it.
136 *
137 * If true, and if flickable is an item view (e.g. ListView, GridView), it will be possible
138 * to navigate the view current items with keyboard up/down arrow buttons.
139 * Also, any key event will be forwarded to the current list item.
140 *
141 * default: ``true``
142 */
143 property bool keyboardNavigationEnabled: true
144//END properties
145
146 implicitWidth: flickable?.contentItem?.implicitWidth
147 ?? Math.max(implicitBackgroundWidth + leftInset + rightInset,
148 contentWidth + leftPadding + rightPadding,
149 implicitHeaderWidth,
150 implicitFooterWidth)
151
152 implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
153 contentHeight + topPadding + bottomPadding
154 + (implicitHeaderHeight > 0 ? implicitHeaderHeight + spacing : 0)
155 + (implicitFooterHeight > 0 ? implicitFooterHeight + spacing : 0))
156
157 contentHeight: flickable?.contentHeight ?? 0
158
159 Kirigami.Theme.inherit: false
160 Kirigami.Theme.colorSet: flickable?.hasOwnProperty("model") ? Kirigami.Theme.View : Kirigami.Theme.Window
161
162 Keys.forwardTo: {
163 if (root.keyboardNavigationEnabled && root.flickable) {
164 if (("currentItem" in root.flickable) && root.flickable.currentItem) {
165 return [ root.flickable.currentItem, root.flickable ];
166 } else {
167 return [ root.flickable ];
168 }
169 } else {
170 return [];
171 }
172 }
173
174 contentItem: QQC2.ScrollView {
175 id: scrollView
176 anchors {
177 top: root.header?.visible
178 ? root.header.bottom
179 : parent.top
180 bottom: root.footer?.visible ? root.footer.top : parent.bottom
181 left: parent.left
182 right: parent.right
183 }
184 clip: true
185 QQC2.ScrollBar.horizontal.policy: root.horizontalScrollBarPolicy
186 QQC2.ScrollBar.horizontal.interactive: root.horizontalScrollBarInteractive
187 QQC2.ScrollBar.vertical.policy: root.verticalScrollBarPolicy
188 QQC2.ScrollBar.vertical.interactive: root.verticalScrollBarInteractive
189 }
190
191 data: [
192 // Has to be a MouseArea that accepts events otherwise touch events on Wayland will get lost
193 MouseArea {
194 id: scrollingArea
195 width: root.horizontalScrollBarPolicy === QQC2.ScrollBar.AlwaysOff ? root.flickable.width : Math.max(root.flickable.width, implicitWidth)
196 height: Math.max(root.flickable.height, implicitHeight)
197 implicitWidth: {
198 let implicit = 0;
199 for (const child of itemsParent.visibleChildren) {
200 if (child.implicitWidth <= 0) {
201 implicit = Math.max(implicit, child.width);
202 } else {
203 implicit = Math.max(implicit, child.implicitWidth);
204 }
205 }
206 return implicit + itemsParent.anchors.leftMargin + itemsParent.anchors.rightMargin;
207 }
208 implicitHeight: {
209 let implicit = 0;
210 for (const child of itemsParent.visibleChildren) {
211 if (child.implicitHeight <= 0) {
212 implicit = Math.max(implicit, child.height);
213 } else {
214 implicit = Math.max(implicit, child.implicitHeight);
215 }
216 }
217 return implicit + itemsParent.anchors.topMargin + itemsParent.anchors.bottomMargin;
218 }
219 Item {
220 id: itemsParent
221 property Flickable flickable
222 anchors {
223 fill: parent
224 topMargin: root.topPadding
225 leftMargin: root.leftPadding
226 rightMargin: root.rightPadding
227 bottomMargin: root.bottomPadding
228 }
229 onChildrenChanged: {
230 const child = children[children.length - 1];
231 if (child instanceof QQC2.ScrollView) {
232 print("Warning: it's not supported to have ScrollViews inside a ScrollablePage")
233 }
234 }
235 }
236 Binding {
237 target: root.flickable
238 property: "bottomMargin"
239 value: root.bottomPadding
240 restoreMode: Binding.RestoreBinding
241 }
242 },
243
244 Loader {
245 id: busyIndicatorLoader
246 active: root.supportsRefreshing
247 sourceComponent: PullDownIndicator {
248 parent: root
249 active: root.refreshing
250 onTriggered: root.refreshing = true
251 }
252 }
253 ]
254
255 Component.onCompleted: {
256 let flickableFound = false;
257 for (const child of itemsParent.data) {
258 if (child instanceof Flickable) {
259 // If there were more flickable children, take the last one, as behavior compatibility
260 // with old internal ScrollView
261 child.activeFocusOnTab = true;
262 root.flickable = child;
263 flickableFound = true;
264 if (child instanceof ListView) {
265 child.keyNavigationEnabled = true;
266 child.keyNavigationWraps = false;
267 }
268 } else if (child instanceof Item) {
269 child.anchors.left = itemsParent.left;
270 child.anchors.right = itemsParent.right;
271 } else if (child instanceof KT.OverlaySheet) {
272 // Reparent sheets, needs to be done before Component.onCompleted
273 if (child.parent === itemsParent || child.parent === null) {
274 child.parent = root;
275 }
276 }
277 }
278
279 if (flickableFound) {
280 scrollView.contentItem = root.flickable;
281 root.flickable.parent = scrollView;
282 // The flickable needs focus only if the page didn't already explicitly set focus to some other control (eg a text field in the header)
283 Qt.callLater(() => {
284 if (root.activeFocus) {
285 root.flickable.forceActiveFocus();
286 }
287 });
288 // Some existing code incorrectly uses anchors
289 root.flickable.anchors.fill = undefined;
290 root.flickable.anchors.top = undefined;
291 root.flickable.anchors.left = undefined;
292 root.flickable.anchors.right = undefined;
293 root.flickable.anchors.bottom = undefined;
294 scrollingArea.visible = false;
295 } else {
296 scrollView.contentItem = root.flickable;
297 scrollingArea.parent = root.flickable.contentItem;
298 scrollingArea.visible = true;
299 root.flickable.contentHeight = Qt.binding(() => scrollingArea.implicitHeight - root.flickable.topMargin - root.flickable.bottomMargin);
300 root.flickable.contentWidth = Qt.binding(() => scrollingArea.implicitWidth);
301 scrollView.forceActiveFocus(Qt.TabFocusReason); // QTBUG-44043 : Focus on currentItem instead of pageStack itself
302 }
303 root.flickable.flickableDirection = Flickable.VerticalFlick;
304
305 // HACK: Qt's default flick deceleration is too high, and we can't change it from plasma-integration, see QTBUG-121500
306 root.flickable.flickDeceleration = 1500;
307 root.flickable.maximumFlickVelocity = 5000;
308 }
309}
A pull-down to refresh indicator that can be added to any Flickable or ScrollablePage.
QAction * print(const QObject *recvr, const char *slot, QObject *parent)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Dec 13 2024 11:48:03 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.