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 2.15 0008 import QtQml 2.15 0009 import QtQuick.Controls 2.15 as QQC2 0010 import QtGraphicalEffects 1.0 as GE 0011 import org.kde.kirigami 2.19 as Kirigami 0012 import org.kde.kirigami.templates 2.2 as KT 0013 import "private" 0014 0015 0016 // TODO KF6: undo many workarounds to make existing code work? 0017 0018 /** 0019 * @brief ScrollablePage is a Page that holds scrollable content, such as a QtQuick.ListView. 0020 * 0021 * Scrolling and scrolling indicators will be automatically managed. 0022 * 0023 * Example usage: 0024 * @code{.qml} 0025 * ScrollablePage { 0026 * id: root 0027 * // The page will automatically be scrollable 0028 * Rectangle { 0029 * width: root.width 0030 * height: 99999 0031 * } 0032 * } 0033 * @endcode 0034 * @warning Do not put a QtQuick.Controls.ScrollView inside of a ScrollablePage; 0035 * children of a ScrollablePage are already inside a QtQuick.Controls.ScrollView. 0036 * 0037 * Another behavior added by this class is a "scroll down to refresh" behavior 0038 * It also can give the contents of the flickable to have more top margins in order 0039 * to make possible to scroll down the list to reach it with the thumb while using the 0040 * phone with a single hand. 0041 * 0042 * Implementations should handle the refresh themselves as follows 0043 * 0044 * Example usage: 0045 * @code{.qml} 0046 * Kirigami.ScrollablePage { 0047 * id: view 0048 * supportsRefreshing: true 0049 * onRefreshingChanged: { 0050 * if (refreshing) { 0051 * myModel.refresh(); 0052 * } 0053 * } 0054 * ListView { 0055 * // NOTE: MyModel doesn't come from the components, 0056 * // it's purely an example on how it can be used together 0057 * // some application logic that can update the list model 0058 * // and signals when it's done. 0059 * model: MyModel { 0060 * onRefreshDone: view.refreshing = false; 0061 * } 0062 * delegate: BasicListItem {} 0063 * } 0064 * } 0065 * [...] 0066 * @endcode 0067 */ 0068 Kirigami.Page { 0069 id: root 0070 0071 //BEGIN properties 0072 /** 0073 * @brief This property specifies whether the list is asking for a refresh. 0074 * 0075 * This property will automatically be set to @c true when the user pulls the list down enough, 0076 * which in return, shows a loading spinner. When this is set to true, it signals 0077 * the application logic to start its refresh procedure. 0078 * 0079 * default: ``false`` 0080 * 0081 * @note The application itself will have to set back this property to @c false when done. 0082 */ 0083 property bool refreshing: false 0084 0085 /** 0086 * @brief This property sets whether scrollable page supports "pull down to refresh" behaviour. 0087 * 0088 * default: ``false`` 0089 */ 0090 property bool supportsRefreshing: false 0091 0092 /** 0093 * @brief This property holds the main Flickable item of this page. 0094 * @deprecated here for compatibility; will be removed in KF6. 0095 */ 0096 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 0097 onFlickableChanged: scrollView.contentItem = flickable; 0098 0099 /** 0100 * @brief This property sets the vertical scrollbar policy. 0101 * @property Qt::ScrollBarPolicy verticalScrollBarPolicy 0102 */ 0103 property int verticalScrollBarPolicy 0104 0105 /** 0106 * @brief This property sets the horizontal scrollbar policy. 0107 * @property Qt::ScrollBarPolicy horizontalScrollBarPolicy 0108 */ 0109 property int horizontalScrollBarPolicy: QQC2.ScrollBar.AlwaysOff 0110 0111 default property alias scrollablePageData: itemsParent.data 0112 property alias scrollablePageChildren: itemsParent.children 0113 0114 /* 0115 * @deprecated here for compatibility; will be removed in KF6. 0116 */ 0117 property QtObject mainItem 0118 onMainItemChanged: { 0119 print("Warning: the mainItem property is deprecated"); 0120 scrollablePageData.push(mainItem); 0121 } 0122 0123 /** 0124 * @brief This property sets whether it is possible to navigate the items in a view that support it. 0125 * 0126 * If true, and if the QtQuick.Flickable is in an item view (e.g. QtQuick.ListView, QtQuick.GridView), 0127 * it will be possible to navigate the view current items with keyboard up/down arrow buttons. 0128 * Also, any key event will be forwarded to the current list item. 0129 * 0130 * default: ``true`` 0131 */ 0132 property bool keyboardNavigationEnabled: true 0133 //END properties 0134 0135 contentHeight: flickable ? flickable.contentHeight : 0 0136 implicitHeight: { 0137 let height = contentHeight + topPadding + bottomPadding; 0138 if (header && header.visible) { 0139 height += header.implicitHeight; 0140 } 0141 if (footer && footer.visible) { 0142 height += footer.implicitHeight; 0143 } 0144 return height; 0145 } 0146 0147 implicitWidth: { 0148 if (flickable) { 0149 if (flickable.contentItem) { 0150 return flickable.contentItem.implicitWidth; 0151 } else { 0152 return contentItem.implicitWidth + leftPadding + rightPadding; 0153 } 0154 } else { 0155 return 0; 0156 } 0157 } 0158 0159 Kirigami.Theme.inherit: false 0160 Kirigami.Theme.colorSet: flickable && flickable.hasOwnProperty("model") ? Kirigami.Theme.View : Kirigami.Theme.Window 0161 0162 Keys.forwardTo: { 0163 if (root.keyboardNavigationEnabled && root.flickable) { 0164 if (("currentItem" in root.flickable) && root.flickable.currentItem) { 0165 return [ root.flickable.currentItem, root.flickable ]; 0166 } else { 0167 return [ root.flickable ]; 0168 } 0169 } else { 0170 return []; 0171 } 0172 } 0173 0174 contentItem: QQC2.ScrollView { 0175 id: scrollView 0176 anchors { 0177 top: (root.header && root.header.visible) 0178 ? root.header.bottom 0179 // FIXME: for now assuming globalToolBarItem is in a Loader, which needs to be get rid of 0180 : (globalToolBarItem && globalToolBarItem.parent && globalToolBarItem.visible 0181 ? globalToolBarItem.parent.bottom 0182 : parent.top) 0183 bottom: (root.footer && root.footer.visible) ? root.footer.top : parent.bottom 0184 left: parent.left 0185 right: parent.right 0186 topMargin: root.refreshing ? busyIndicatorLoader.height : 0 0187 Behavior on topMargin { 0188 NumberAnimation { 0189 easing.type: Easing.InOutQuad 0190 duration: Kirigami.Units.longDuration 0191 } 0192 } 0193 } 0194 QQC2.ScrollBar.horizontal.policy: root.horizontalScrollBarPolicy 0195 QQC2.ScrollBar.vertical.policy: root.verticalScrollBarPolicy 0196 } 0197 0198 data: [ 0199 // Has to be a MouseArea that accepts events otherwise touch events on Wayland will get lost 0200 MouseArea { 0201 id: scrollingArea 0202 width: root.flickable.width 0203 height: Math.max(root.flickable.height, implicitHeight) 0204 implicitHeight: { 0205 let impl = 0; 0206 for (const i in itemsParent.visibleChildren) { 0207 const child = itemsParent.visibleChildren[i]; 0208 if (child.implicitHeight <= 0) { 0209 impl = Math.max(impl, child.height); 0210 } else { 0211 impl = Math.max(impl, child.implicitHeight); 0212 } 0213 } 0214 return impl + itemsParent.anchors.topMargin + itemsParent.anchors.bottomMargin; 0215 } 0216 Item { 0217 id: itemsParent 0218 property Flickable flickable 0219 anchors { 0220 fill: parent 0221 leftMargin: root.leftPadding 0222 topMargin: root.topPadding 0223 rightMargin: root.rightPadding 0224 bottomMargin: root.bottomPadding 0225 } 0226 onChildrenChanged: { 0227 const child = children[children.length - 1]; 0228 if (child instanceof QQC2.ScrollView) { 0229 print("Warning: it's not supported to have ScrollViews inside a ScrollablePage") 0230 } 0231 } 0232 } 0233 Binding { 0234 target: root.flickable 0235 property: "bottomMargin" 0236 value: root.bottomPadding 0237 restoreMode: Binding.RestoreBinding 0238 } 0239 }, 0240 0241 Loader { 0242 id: busyIndicatorLoader 0243 z: 99 0244 y: root.flickable.verticalLayoutDirection === ListView.BottomToTop 0245 ? -root.flickable.contentY + root.flickable.originY + height 0246 : -root.flickable.contentY + root.flickable.originY - height 0247 width: root.flickable.width 0248 height: Kirigami.Units.gridUnit * 4 0249 active: root.supportsRefreshing 0250 0251 sourceComponent: Item { 0252 id: busyIndicatorFrame 0253 0254 QQC2.BusyIndicator { 0255 id: busyIndicator 0256 z: 1 0257 anchors.centerIn: parent 0258 running: root.refreshing 0259 visible: root.refreshing 0260 // Android busywidget QQC seems to be broken at custom sizes 0261 } 0262 Rectangle { 0263 id: spinnerProgress 0264 anchors { 0265 fill: busyIndicator 0266 margins: Kirigami.Units.smallSpacing 0267 } 0268 radius: width 0269 visible: supportsRefreshing && !refreshing && progress > 0 0270 color: "transparent" 0271 opacity: 0.8 0272 border.color: Kirigami.Theme.backgroundColor 0273 border.width: Kirigami.Units.smallSpacing 0274 property real progress: supportsRefreshing && !refreshing ? (busyIndicatorLoader.y / busyIndicatorFrame.height) : 0 0275 } 0276 GE.ConicalGradient { 0277 source: spinnerProgress 0278 visible: spinnerProgress.visible 0279 anchors.fill: spinnerProgress 0280 gradient: Gradient { 0281 GradientStop { position: 0.00; color: Kirigami.Theme.highlightColor } 0282 GradientStop { position: spinnerProgress.progress; color: Kirigami.Theme.highlightColor } 0283 GradientStop { position: spinnerProgress.progress + 0.01; color: "transparent" } 0284 GradientStop { position: 1.00; color: "transparent" } 0285 } 0286 } 0287 0288 Connections { 0289 target: busyIndicatorLoader 0290 function onYChanged() { 0291 if (!supportsRefreshing) { 0292 return; 0293 } 0294 0295 if (!root.refreshing && busyIndicatorLoader.y > busyIndicatorFrame.height / 2 + topPadding) { 0296 refreshTriggerTimer.running = true; 0297 } else { 0298 refreshTriggerTimer.running = false; 0299 } 0300 } 0301 } 0302 Timer { 0303 id: refreshTriggerTimer 0304 interval: 500 0305 onTriggered: { 0306 if (!root.refreshing && busyIndicatorLoader.y > busyIndicatorFrame.height / 2 + topPadding) { 0307 root.refreshing = true; 0308 } 0309 } 0310 } 0311 } 0312 } 0313 ] 0314 0315 Component.onCompleted: { 0316 let flickableFound = false; 0317 for (const i in itemsParent.data) { 0318 const child = itemsParent.data[i]; 0319 if (child instanceof Flickable) { 0320 // If there were more flickable children, take the last one, as behavior compatibility 0321 // with old internal ScrollView 0322 child.activeFocusOnTab = true; 0323 root.flickable = child; 0324 flickableFound = true; 0325 if (child instanceof ListView) { 0326 child.keyNavigationEnabled = true; 0327 child.keyNavigationWraps = false; 0328 } 0329 } else if (child instanceof Item) { 0330 child.anchors.left = itemsParent.left; 0331 child.anchors.right = itemsParent.right; 0332 } else if (child instanceof KT.OverlaySheet) { 0333 // Reparent sheets, needs to be done before Component.onCompleted 0334 if (child.parent === itemsParent || child.parent === null) { 0335 child.parent = root; 0336 } 0337 } 0338 } 0339 0340 if (flickableFound) { 0341 scrollView.contentItem = root.flickable; 0342 root.flickable.parent = scrollView; 0343 // 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) 0344 Qt.callLater(() => { 0345 if (root.activeFocus) { 0346 root.flickable.forceActiveFocus(); 0347 } 0348 }); 0349 // Some existing code incorrectly uses anchors 0350 root.flickable.anchors.fill = undefined; 0351 root.flickable.anchors.left = undefined; 0352 root.flickable.anchors.right = undefined; 0353 root.flickable.anchors.top = undefined; 0354 root.flickable.anchors.bottom = undefined; 0355 scrollingArea.visible = false; 0356 } else { 0357 scrollView.contentItem = root.flickable; 0358 scrollingArea.parent = root.flickable.contentItem; 0359 scrollingArea.visible = true; 0360 root.flickable.contentHeight = Qt.binding(() => scrollingArea.implicitHeight - root.flickable.topMargin - root.flickable.bottomMargin); 0361 root.flickable.contentWidth = Qt.binding(() => scrollingArea.implicitWidth); 0362 } 0363 root.flickable.flickableDirection = Flickable.VerticalFlick; 0364 } 0365 }