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 }