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 
7 import QtQuick
8 import QtQml
9 import QtQuick.Controls as QQC2
10 import org.kde.kirigami as Kirigami
11 import org.kde.kirigami.templates as KT
12 import "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  */
67 Kirigami.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 }
QTextStream & right(QTextStream &stream)
QAction * print(const QObject *recvr, const char *slot, QObject *parent)
QTextStream & left(QTextStream &stream)
QTextStream & left(QTextStream &s)
QTextStream & right(QTextStream &s)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Thu Feb 15 2024 03:59:21 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.