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 This property sets the horizontal scrollbar policy.
106 * @property Qt::ScrollBarPolicy horizontalScrollBarPolicy
107 */
108 property int horizontalScrollBarPolicy: QQC2.ScrollBar.AlwaysOff
109
110 default property alias scrollablePageData: itemsParent.data
111 property alias scrollablePageChildren: itemsParent.children
112
113 /*
114 * @deprecated here for compatibility; will be removed in KF6.
115 */
116 property QtObject mainItem
117 onMainItemChanged: {
118 print("Warning: the mainItem property is deprecated");
119 scrollablePageData.push(mainItem);
120 }
121
122 /**
123 * @brief This property sets whether it is possible to navigate the items in a view that support it.
124 *
125 * If true, and if flickable is an item view (e.g. ListView, GridView), it will be possible
126 * to navigate the view current items with keyboard up/down arrow buttons.
127 * Also, any key event will be forwarded to the current list item.
128 *
129 * default: ``true``
130 */
131 property bool keyboardNavigationEnabled: true
132//END properties
133
134 implicitWidth: flickable?.contentItem?.implicitWidth
135 ?? Math.max(implicitBackgroundWidth + leftInset + rightInset,
136 contentWidth + leftPadding + rightPadding,
137 implicitHeaderWidth,
138 implicitFooterWidth)
139
140 implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
141 contentHeight + topPadding + bottomPadding
142 + (implicitHeaderHeight > 0 ? implicitHeaderHeight + spacing : 0)
143 + (implicitFooterHeight > 0 ? implicitFooterHeight + spacing : 0))
144
145 contentHeight: flickable?.contentHeight ?? 0
146
147 Kirigami.Theme.inherit: false
148 Kirigami.Theme.colorSet: flickable?.hasOwnProperty("model") ? Kirigami.Theme.View : Kirigami.Theme.Window
149
150 Keys.forwardTo: {
151 if (root.keyboardNavigationEnabled && root.flickable) {
152 if (("currentItem" in root.flickable) && root.flickable.currentItem) {
153 return [ root.flickable.currentItem, root.flickable ];
154 } else {
155 return [ root.flickable ];
156 }
157 } else {
158 return [];
159 }
160 }
161
162 contentItem: QQC2.ScrollView {
163 id: scrollView
164 anchors {
165 top: root.header?.visible
166 ? root.header.bottom
167 : parent.top
168 bottom: root.footer?.visible ? root.footer.top : parent.bottom
169 left: parent.left
170 right: parent.right
171 }
172 clip: true
173 QQC2.ScrollBar.horizontal.policy: root.horizontalScrollBarPolicy
174 QQC2.ScrollBar.vertical.policy: root.verticalScrollBarPolicy
175 }
176
177 data: [
178 // Has to be a MouseArea that accepts events otherwise touch events on Wayland will get lost
179 MouseArea {
180 id: scrollingArea
181 width: root.horizontalScrollBarPolicy === QQC2.ScrollBar.AlwaysOff ? root.flickable.width : Math.max(root.flickable.width, implicitWidth)
182 height: Math.max(root.flickable.height, implicitHeight)
183 implicitWidth: {
184 let implicit = 0;
185 for (const child of itemsParent.visibleChildren) {
186 if (child.implicitWidth <= 0) {
187 implicit = Math.max(implicit, child.width);
188 } else {
189 implicit = Math.max(implicit, child.implicitWidth);
190 }
191 }
192 return implicit + itemsParent.anchors.leftMargin + itemsParent.anchors.rightMargin;
193 }
194 implicitHeight: {
195 let implicit = 0;
196 for (const child of itemsParent.visibleChildren) {
197 if (child.implicitHeight <= 0) {
198 implicit = Math.max(implicit, child.height);
199 } else {
200 implicit = Math.max(implicit, child.implicitHeight);
201 }
202 }
203 return implicit + itemsParent.anchors.topMargin + itemsParent.anchors.bottomMargin;
204 }
205 Item {
206 id: itemsParent
207 property Flickable flickable
208 anchors {
209 fill: parent
210 topMargin: root.topPadding
211 leftMargin: root.leftPadding
212 rightMargin: root.rightPadding
213 bottomMargin: root.bottomPadding
214 }
215 onChildrenChanged: {
216 const child = children[children.length - 1];
217 if (child instanceof QQC2.ScrollView) {
218 print("Warning: it's not supported to have ScrollViews inside a ScrollablePage")
219 }
220 }
221 }
222 Binding {
223 target: root.flickable
224 property: "bottomMargin"
225 value: root.bottomPadding
226 restoreMode: Binding.RestoreBinding
227 }
228 },
229
230 Loader {
231 id: busyIndicatorLoader
232 active: root.supportsRefreshing
233 sourceComponent: PullDownIndicator {
234 parent: root
235 active: root.refreshing
236 onTriggered: root.refreshing = true
237 }
238 }
239 ]
240
241 Component.onCompleted: {
242 let flickableFound = false;
243 for (const child of itemsParent.data) {
244 if (child instanceof Flickable) {
245 // If there were more flickable children, take the last one, as behavior compatibility
246 // with old internal ScrollView
247 child.activeFocusOnTab = true;
248 root.flickable = child;
249 flickableFound = true;
250 if (child instanceof ListView) {
251 child.keyNavigationEnabled = true;
252 child.keyNavigationWraps = false;
253 }
254 } else if (child instanceof Item) {
255 child.anchors.left = itemsParent.left;
256 child.anchors.right = itemsParent.right;
257 } else if (child instanceof KT.OverlaySheet) {
258 // Reparent sheets, needs to be done before Component.onCompleted
259 if (child.parent === itemsParent || child.parent === null) {
260 child.parent = root;
261 }
262 }
263 }
264
265 if (flickableFound) {
266 scrollView.contentItem = root.flickable;
267 root.flickable.parent = scrollView;
268 // 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)
269 Qt.callLater(() => {
270 if (root.activeFocus) {
271 root.flickable.forceActiveFocus();
272 }
273 });
274 // Some existing code incorrectly uses anchors
275 root.flickable.anchors.fill = undefined;
276 root.flickable.anchors.top = undefined;
277 root.flickable.anchors.left = undefined;
278 root.flickable.anchors.right = undefined;
279 root.flickable.anchors.bottom = undefined;
280 scrollingArea.visible = false;
281 } else {
282 scrollView.contentItem = root.flickable;
283 scrollingArea.parent = root.flickable.contentItem;
284 scrollingArea.visible = true;
285 root.flickable.contentHeight = Qt.binding(() => scrollingArea.implicitHeight - root.flickable.topMargin - root.flickable.bottomMargin);
286 root.flickable.contentWidth = Qt.binding(() => scrollingArea.implicitWidth);
287 scrollView.forceActiveFocus(Qt.TabFocusReason); // QTBUG-44043 : Focus on currentItem instead of pageStack itself
288 }
289 root.flickable.flickableDirection = Flickable.VerticalFlick;
290
291 // HACK: Qt's default flick deceleration is too high, and we can't change it from plasma-integration, see QTBUG-121500
292 root.flickable.flickDeceleration = 1500;
293 root.flickable.maximumFlickVelocity = 5000;
294 }
295}
A pull-down to refresh indicator that can be added to any Flickable or ScrollablePage.
KGuiItem print()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jun 14 2024 11:53:34 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.