Warning, /frameworks/kirigami/src/controls/ScrollablePage.qml is written in an unsupported language. File is not indexed.
0001 /*
0002 * SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
0003 *
0004 * SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006
0007 import QtQuick
0008 import QtQml
0009 import QtQuick.Controls as QQC2
0010 import org.kde.kirigami as Kirigami
0011 import org.kde.kirigami.templates as KT
0012 import "private"
0013
0014
0015 // TODO KF6: undo many workarounds to make existing code work?
0016
0017 /**
0018 * @brief ScrollablePage is a Page that holds scrollable content, such as a ListView.
0019 *
0020 * Scrolling and scrolling indicators will be automatically managed.
0021 *
0022 * Example usage:
0023 * @code
0024 * ScrollablePage {
0025 * id: root
0026 * // The page will automatically be scrollable
0027 * Rectangle {
0028 * width: root.width
0029 * height: 99999
0030 * }
0031 * }
0032 * @endcode
0033 *
0034 * @warning Do not put a ScrollView inside of a ScrollablePage; children of a ScrollablePage are already inside a ScrollView.
0035 *
0036 * Another behavior added by this class is a "scroll down to refresh" behavior
0037 * It also can give the contents of the flickable to have more top margins in order
0038 * to make possible to scroll down the list to reach it with the thumb while using the
0039 * phone with a single hand.
0040 *
0041 * Implementations should handle the refresh themselves as follows
0042 *
0043 * Example usage:
0044 * @code
0045 * Kirigami.ScrollablePage {
0046 * id: view
0047 * supportsRefreshing: true
0048 * onRefreshingChanged: {
0049 * if (refreshing) {
0050 * myModel.refresh();
0051 * }
0052 * }
0053 * ListView {
0054 * // NOTE: MyModel doesn't come from the components,
0055 * // it's purely an example on how it can be used together
0056 * // some application logic that can update the list model
0057 * // and signals when it's done.
0058 * model: MyModel {
0059 * onRefreshDone: view.refreshing = false;
0060 * }
0061 * delegate: ItemDelegate {}
0062 * }
0063 * }
0064 * [...]
0065 * @endcode
0066 */
0067 Kirigami.Page {
0068 id: root
0069
0070 //BEGIN properties
0071 /**
0072 * @brief This property tells whether the list is asking for a refresh.
0073 *
0074 * This property will automatically be set to true when the user pulls the list down enough,
0075 * which in return, shows a loading spinner. When this is set to true, it signals
0076 * the application logic to start its refresh procedure.
0077 *
0078 * default: ``false``
0079 *
0080 * @note The application itself will have to set back this property to false when done.
0081 */
0082 property bool refreshing: false
0083
0084 /**
0085 * @brief This property sets whether scrollable page supports "pull down to refresh" behaviour.
0086 *
0087 * default: ``false``
0088 */
0089 property bool supportsRefreshing: false
0090
0091 /**
0092 * @brief This property holds the main Flickable item of this page.
0093 * @deprecated here for compatibility; will be removed in KF6.
0094 */
0095 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
0096 onFlickableChanged: scrollView.contentItem = flickable;
0097
0098 /**
0099 * @brief This property sets the vertical scrollbar policy.
0100 * @property Qt::ScrollBarPolicy verticalScrollBarPolicy
0101 */
0102 property int verticalScrollBarPolicy
0103
0104 /**
0105 * @brief This property sets the horizontal scrollbar policy.
0106 * @property Qt::ScrollBarPolicy horizontalScrollBarPolicy
0107 */
0108 property int horizontalScrollBarPolicy: QQC2.ScrollBar.AlwaysOff
0109
0110 default property alias scrollablePageData: itemsParent.data
0111 property alias scrollablePageChildren: itemsParent.children
0112
0113 /*
0114 * @deprecated here for compatibility; will be removed in KF6.
0115 */
0116 property QtObject mainItem
0117 onMainItemChanged: {
0118 print("Warning: the mainItem property is deprecated");
0119 scrollablePageData.push(mainItem);
0120 }
0121
0122 /**
0123 * @brief This property sets whether it is possible to navigate the items in a view that support it.
0124 *
0125 * If true, and if flickable is an item view (e.g. ListView, GridView), it will be possible
0126 * to navigate the view current items with keyboard up/down arrow buttons.
0127 * Also, any key event will be forwarded to the current list item.
0128 *
0129 * default: ``true``
0130 */
0131 property bool keyboardNavigationEnabled: true
0132 //END properties
0133
0134 implicitWidth: flickable?.contentItem?.implicitWidth
0135 ?? Math.max(implicitBackgroundWidth + leftInset + rightInset,
0136 contentWidth + leftPadding + rightPadding,
0137 implicitHeaderWidth,
0138 implicitFooterWidth)
0139
0140 implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
0141 contentHeight + topPadding + bottomPadding
0142 + (implicitHeaderHeight > 0 ? implicitHeaderHeight + spacing : 0)
0143 + (implicitFooterHeight > 0 ? implicitFooterHeight + spacing : 0))
0144
0145 contentHeight: flickable?.contentHeight ?? 0
0146
0147 Kirigami.Theme.inherit: false
0148 Kirigami.Theme.colorSet: flickable?.hasOwnProperty("model") ? Kirigami.Theme.View : Kirigami.Theme.Window
0149
0150 Keys.forwardTo: {
0151 if (root.keyboardNavigationEnabled && root.flickable) {
0152 if (("currentItem" in root.flickable) && root.flickable.currentItem) {
0153 return [ root.flickable.currentItem, root.flickable ];
0154 } else {
0155 return [ root.flickable ];
0156 }
0157 } else {
0158 return [];
0159 }
0160 }
0161
0162 contentItem: QQC2.ScrollView {
0163 id: scrollView
0164 anchors {
0165 top: root.header?.visible
0166 ? root.header.bottom
0167 : parent.top
0168 bottom: root.footer?.visible ? root.footer.top : parent.bottom
0169 left: parent.left
0170 right: parent.right
0171 }
0172 clip: true
0173 QQC2.ScrollBar.horizontal.policy: root.horizontalScrollBarPolicy
0174 QQC2.ScrollBar.vertical.policy: root.verticalScrollBarPolicy
0175 }
0176
0177 data: [
0178 // Has to be a MouseArea that accepts events otherwise touch events on Wayland will get lost
0179 MouseArea {
0180 id: scrollingArea
0181 width: root.horizontalScrollBarPolicy === QQC2.ScrollBar.AlwaysOff ? root.flickable.width : Math.max(root.flickable.width, implicitWidth)
0182 height: Math.max(root.flickable.height, implicitHeight)
0183 implicitWidth: {
0184 let implicit = 0;
0185 for (const child of itemsParent.visibleChildren) {
0186 if (child.implicitWidth <= 0) {
0187 implicit = Math.max(implicit, child.width);
0188 } else {
0189 implicit = Math.max(implicit, child.implicitWidth);
0190 }
0191 }
0192 return implicit + itemsParent.anchors.leftMargin + itemsParent.anchors.rightMargin;
0193 }
0194 implicitHeight: {
0195 let implicit = 0;
0196 for (const child of itemsParent.visibleChildren) {
0197 if (child.implicitHeight <= 0) {
0198 implicit = Math.max(implicit, child.height);
0199 } else {
0200 implicit = Math.max(implicit, child.implicitHeight);
0201 }
0202 }
0203 return implicit + itemsParent.anchors.topMargin + itemsParent.anchors.bottomMargin;
0204 }
0205 Item {
0206 id: itemsParent
0207 property Flickable flickable
0208 anchors {
0209 fill: parent
0210 topMargin: root.topPadding
0211 leftMargin: root.leftPadding
0212 rightMargin: root.rightPadding
0213 bottomMargin: root.bottomPadding
0214 }
0215 onChildrenChanged: {
0216 const child = children[children.length - 1];
0217 if (child instanceof QQC2.ScrollView) {
0218 print("Warning: it's not supported to have ScrollViews inside a ScrollablePage")
0219 }
0220 }
0221 }
0222 Binding {
0223 target: root.flickable
0224 property: "bottomMargin"
0225 value: root.bottomPadding
0226 restoreMode: Binding.RestoreBinding
0227 }
0228 },
0229
0230 Loader {
0231 id: busyIndicatorLoader
0232 active: root.supportsRefreshing
0233 sourceComponent: PullDownIndicator {
0234 parent: root
0235 active: root.refreshing
0236 onTriggered: root.refreshing = true
0237 }
0238 }
0239 ]
0240
0241 Component.onCompleted: {
0242 let flickableFound = false;
0243 for (const child of itemsParent.data) {
0244 if (child instanceof Flickable) {
0245 // If there were more flickable children, take the last one, as behavior compatibility
0246 // with old internal ScrollView
0247 child.activeFocusOnTab = true;
0248 root.flickable = child;
0249 flickableFound = true;
0250 if (child instanceof ListView) {
0251 child.keyNavigationEnabled = true;
0252 child.keyNavigationWraps = false;
0253 }
0254 } else if (child instanceof Item) {
0255 child.anchors.left = itemsParent.left;
0256 child.anchors.right = itemsParent.right;
0257 } else if (child instanceof KT.OverlaySheet) {
0258 // Reparent sheets, needs to be done before Component.onCompleted
0259 if (child.parent === itemsParent || child.parent === null) {
0260 child.parent = root;
0261 }
0262 }
0263 }
0264
0265 if (flickableFound) {
0266 scrollView.contentItem = root.flickable;
0267 root.flickable.parent = scrollView;
0268 // 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)
0269 Qt.callLater(() => {
0270 if (root.activeFocus) {
0271 root.flickable.forceActiveFocus();
0272 }
0273 });
0274 // Some existing code incorrectly uses anchors
0275 root.flickable.anchors.fill = undefined;
0276 root.flickable.anchors.top = undefined;
0277 root.flickable.anchors.left = undefined;
0278 root.flickable.anchors.right = undefined;
0279 root.flickable.anchors.bottom = undefined;
0280 scrollingArea.visible = false;
0281 } else {
0282 scrollView.contentItem = root.flickable;
0283 scrollingArea.parent = root.flickable.contentItem;
0284 scrollingArea.visible = true;
0285 root.flickable.contentHeight = Qt.binding(() => scrollingArea.implicitHeight - root.flickable.topMargin - root.flickable.bottomMargin);
0286 root.flickable.contentWidth = Qt.binding(() => scrollingArea.implicitWidth);
0287 scrollView.forceActiveFocus(Qt.TabFocusReason); // QTBUG-44043 : Focus on currentItem instead of pageStack itself
0288 }
0289 root.flickable.flickableDirection = Flickable.VerticalFlick;
0290 }
0291 }