Warning, /frameworks/kirigami/src/controls/PageRow.qml is written in an unsupported language. File is not indexed.

0001 /*
0002  *  SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
0003  *
0004  *  SPDX-License-Identifier: LGPL-2.0-or-later
0005  */
0006 
0007 import QtQuick
0008 import QtQuick.Layouts
0009 import QtQuick.Templates as QT
0010 import QtQuick.Controls as QQC2
0011 import org.kde.kirigami as Kirigami
0012 import "private/globaltoolbar" as GlobalToolBar
0013 import "templates" as KT
0014 
0015 /**
0016  * PageRow implements a row-based navigation model, which can be used
0017  * with a set of interlinked information pages. Pages are pushed in the
0018  * back of the row and the view scrolls until that row is visualized.
0019  * A PageRow can show a single page or a multiple set of columns, depending
0020  * on the window width: on a phone a single column should be fullscreen,
0021  * while on a tablet or a desktop more than one column should be visible.
0022  *
0023  * @inherits QtQuick.Templates.Control
0024  */
0025 QT.Control {
0026     id: root
0027 
0028 //BEGIN PROPERTIES
0029     /**
0030      * @brief This property holds the number of pages currently pushed onto the view.
0031      * @property int depth
0032      */
0033     readonly property alias depth: columnView.count
0034 
0035     /**
0036      * @brief This property holds the last page in the row.
0037      * @property Page lastItem
0038      */
0039     readonly property Item lastItem: columnView.contentChildren.length > 0 ?  columnView.contentChildren[columnView.contentChildren.length - 1] : null
0040 
0041     /**
0042      * @brief This property holds the currently visible/active page.
0043      *
0044      * Because of the ability to display multiple pages, it will hold the currently active page.
0045      *
0046      * @property Page currentItem
0047      */
0048     readonly property alias currentItem: columnView.currentItem
0049 
0050     /**
0051      * @brief This property holds the index of the currently active page.
0052      * @see currentItem
0053      * @property int currentIndex
0054      */
0055     property alias currentIndex: columnView.currentIndex
0056 
0057     /**
0058      * @brief This property sets the initial page for this PageRow.
0059      * @property Page initialPage
0060      */
0061     property var initialPage
0062 
0063     /**
0064      * @brief This property holds the main ColumnView of this Row.
0065      * @property ColumnView contentItem
0066      */
0067     contentItem: columnView
0068 
0069     /**
0070      * @brief This property holds the ColumnView that this PageRow owns.
0071      *
0072      * Generally, you shouldn't need to change the value of this property.
0073      *
0074      * @property ColumnView columnView
0075      * @since 2.12
0076      */
0077     property alias columnView: columnView
0078 
0079     /**
0080      * @brief This property holds the present pages in the PageRow.
0081      * @property list<Page> items
0082      * @since 2.6
0083      */
0084     readonly property alias items: columnView.contentChildren;
0085 
0086     /**
0087      * @brief This property holds all visible pages in the PageRow,
0088      * excluding those which are scrolled away.
0089      * @property list<Page> visibleItems
0090      * @since 2.6
0091      */
0092     readonly property alias visibleItems: columnView.visibleItems
0093 
0094     /**
0095      * @brief This property holds the first page in the PageRow that is at least partially visible.
0096      * @note Pages before that one (the one contained in the property) will be out of the viewport.
0097      * @see ColumnView::leadingVisibleItem
0098      * @property Item leadingVisibleItem
0099      * @since 2.6
0100      */
0101     readonly property alias leadingVisibleItem: columnView.leadingVisibleItem
0102 
0103     /**
0104      * @brief This property holds the last page in the PageRow that is at least partially visible.
0105      * @note Pages after that one (the one contained in the property) will be out of the viewport.
0106      * @see ColumnView::trailingVisibleItem
0107      * @property Item trailingVisibleItem
0108      * @since 2.6
0109      */
0110     readonly property alias trailingVisibleItem: columnView.trailingVisibleItem
0111 
0112     /**
0113      * @brief This property holds the default width for a column.
0114      *
0115      * default: ``20 * Kirigami.Units.gridUnit``
0116      *
0117      * @note Pages can override it using implicitWidth, Layout.fillWidth, Layout.minimumWidth etc.
0118      */
0119     property int defaultColumnWidth: Kirigami.Units.gridUnit * 20
0120 
0121     /**
0122      * @brief This property sets whether it is possible to go back/forward
0123      * by swiping with a gesture on the content view.
0124      *
0125      * default: ``true``
0126      *
0127      * @property bool interactive
0128      */
0129     property alias interactive: columnView.interactive
0130 
0131     /**
0132      * @brief This property tells whether the PageRow is wide enough to show multiple pages.
0133      * @since 5.37
0134      */
0135     readonly property bool wideMode: width >= defaultColumnWidth * 2 && depth >= 2
0136 
0137     /**
0138      * @brief This property sets whether the separators between pages should be displayed.
0139      *
0140      * default: ``true``
0141      *
0142      * @property bool separatorVisible
0143      * @since 5.38
0144      */
0145     property alias separatorVisible: columnView.separatorVisible
0146 
0147     /**
0148      * @brief This property sets the appearance of an optional global toolbar for the whole PageRow.
0149      *
0150      * It's a grouped property comprised of the following properties:
0151      * * style (``Kirigami.ApplicationHeaderStyle``): can have the following values:
0152      *  * ``Auto``: Depending on application formfactor, it can behave automatically like other values, such as a Breadcrumb on mobile and ToolBar on desktop.
0153      *  * ``Breadcrumb``: It will show a breadcrumb of all the page titles in the stack, for easy navigation.
0154      *  * ``Titles``: Each page will only have its own title on top.
0155      *  * ``ToolBar``: Each page will have the title on top together buttons and menus to represent all of the page actions. Not available on Mobile systems.
0156      *  * ``None``: No global toolbar will be shown.
0157      *
0158      * * ``actualStyle``: This will represent the actual style of the toolbar; it can be different from style in the case style is Auto.
0159      * * ``showNavigationButtons``: OR flags combination of Kirigami.ApplicationHeaderStyle.ShowBackButton and Kirigami.ApplicationHeaderStyle.ShowForwardButton.
0160      * * ``toolbarActionAlignment: Qt::Alignment``: How to horizontally align the actions when using the ToolBar style. Note that anything but Qt.AlignRight will cause the title to be hidden (default: ``Qt.AlignRight``).
0161      * * ``minimumHeight: int`` Minimum height of the header, which will be resized when scrolling. Only in Mobile mode (default: ``preferredHeight``, sliding but no scaling).
0162      * * ``preferredHeight: int`` The height the toolbar will usually have.
0163      * * ``leftReservedSpace: int, readonly`` How many pixels of extra space are reserved at the left of the page toolbar (typically for navigation buttons or a drawer handle).
0164      * * ``rightReservedSpace: int, readonly`` How many pixels of extra space  are reserved at the right of the page toolbar (typically for a drawer handle).
0165      *
0166      * @property org::kde::kirigami::private::globaltoolbar::PageRowGlobalToolBarStyleGroup globalToolBar
0167      * @since 5.48
0168      */
0169     readonly property alias globalToolBar: globalToolBar
0170 
0171     /**
0172      * @brief This property assigns a drawer as an internal left sidebar for this PageRow.
0173      *
0174      * In this case, when open and not modal, the drawer contents will be in the same layer as the base pagerow.
0175      * Pushing any other layer on top will cover the sidebar.
0176      *
0177      * @since 5.84
0178      */
0179     // TODO KF6: globaldrawer should use actions also used by this sidebar instead of reparenting globaldrawer contents?
0180     property OverlayDrawer leftSidebar
0181 
0182     /**
0183      * @brief This property holds the modal layers.
0184      *
0185      * Sometimes an application needs a modal page that always covers all the rows.
0186      * For instance the full screen image of an image viewer or a settings page.
0187      *
0188      * @property QtQuick.Controls.StackView layers
0189      * @since 5.38
0190      */
0191     property alias layers: layersStack
0192 
0193     /**
0194      * @brief This property holds whether to automatically pop pages at the top of the stack if they are not visible.
0195      *
0196      * If a user navigates to a previous page on the stack (ex. pressing back button) and pages above
0197      * it on the stack are not visible, they will be popped if this property is true.
0198      *
0199      * @since 5.101
0200      */
0201     property bool popHiddenPages: false
0202 //END PROPERTIES
0203 
0204 //BEGIN FUNCTIONS
0205     /**
0206      * @brief This method pushes a page on the stack.
0207      *
0208      * A single page can be defined as an url, a component, or an object. It can
0209      * also be an array of the above said types, but in that case, the
0210      * properties' array length must match pages' array length or it must be
0211      * empty. Failing to comply with the following rules will make the method
0212      * return null before doing anything.
0213      *
0214      * @param page A single page or an array of pages.
0215      * @param properties A single property object or an array of property
0216      * objects.
0217      *
0218      * @return The new created page (or the last one if it was an array).
0219      */
0220     function push(page, properties): QT.Page {
0221         if (!pagesLogic.verifyPages(page, properties)) {
0222             console.warn("Pushed pages do not conform to the rules. Please check the documentation.");
0223             console.trace();
0224             return null
0225         }
0226 
0227         const item = pagesLogic.insertPage_unchecked(currentIndex + 1, page, properties)
0228         currentIndex = depth - 1
0229         return item
0230     }
0231 
0232     /**
0233      * @brief Pushes a page as a new dialog on desktop and as a layer on mobile.
0234      *
0235      * @param page A single page defined as either a string url, a component or
0236      * an object (which will be reparented). The following page gains
0237      * `closeDialog()` method allowing to make it indistinguishable to
0238      * close/hide it when in desktop or mobile mode. Note that Kiriami supports
0239      * calling `closeDialog()` only once.
0240      *
0241      * @param properties The properties given when initializing the page.
0242      * @param windowProperties The properties given to the initialized window on desktop.
0243      * @return Returns a newly created page.
0244      */
0245     function pushDialogLayer(page, properties = {}, windowProperties = {}): QT.Page {
0246         if (!pagesLogic.verifyPages(page, properties)) {
0247             console.warn("Page pushed as a dialog or layer does not conform to the rules. Please check the documentation.");
0248             console.trace();
0249             return null
0250         }
0251         let item;
0252         if (Kirigami.Settings.isMobile) {
0253             if (QQC2.ApplicationWindow.window.width > Kirigami.Units.gridUnit * 40) {
0254                 // open as a QQC2.Dialog
0255                 const component = pagesLogic.getMobileDialogLayerComponent();
0256                 const dialog = component.createObject(QQC2.Overlay.overlay, {
0257                     width: Qt.binding(() => QQC2.ApplicationWindow.window.width - Kirigami.Units.gridUnit * 5),
0258                     height: Qt.binding(() => QQC2.ApplicationWindow.window.height - Kirigami.Units.gridUnit * 5),
0259                     x: Kirigami.Units.gridUnit * 2.5,
0260                     y: Kirigami.Units.gridUnit * 2.5,
0261                 });
0262 
0263                 if (typeof page === "string") {
0264                     // url => load component and then load item from component
0265                     const component = Qt.createComponent(Qt.resolvedUrl(page));
0266                     item = component.createObject(dialog.contentItem, properties);
0267                     component.destroy();
0268                     dialog.contentItem.contentItem = item
0269                 } else if (page instanceof Component) {
0270                     item = page.createObject(dialog.contentItem, properties);
0271                     dialog.contentItem.contentItem = item
0272                 } else if (page instanceof Item) {
0273                     item = page;
0274                     page.parent = dialog.contentItem;
0275                 }
0276                 dialog.title = Qt.binding(() => item.title);
0277 
0278                 // Pushing a PageRow is supported but without PageRow toolbar
0279                 if (item.globalToolBar && item.globalToolBar.style) {
0280                     item.globalToolBar.style = Kirigami.ApplicationHeaderStyle.None
0281                 }
0282                 Object.defineProperty(item, 'closeDialog', {
0283                     value: function() {
0284                         dialog.close();
0285                     }
0286                 });
0287                 dialog.open();
0288             } else {
0289                 // open as a layer
0290                 item = layers.push(page, properties);
0291                 Object.defineProperty(item, 'closeDialog', {
0292                     value: function() {
0293                         layers.pop();
0294                     }
0295                 });
0296             }
0297         } else {
0298             // open as a new window
0299             if (windowProperties.modality === undefined || windowProperties.modality === null) {
0300                 windowProperties.modality = Qt.WindowModal;
0301             }
0302             if (windowProperties.height === undefined || windowProperties.height === null) {
0303                 windowProperties.height = Kirigami.Units.gridUnit * 30;
0304             }
0305             if (windowProperties.width === undefined || windowProperties.width === null) {
0306                 windowProperties.width = Kirigami.Units.gridUnit * 50;
0307             }
0308             if (windowProperties.minimumWidth === undefined || windowProperties.minimumWidth === null) {
0309                 windowProperties.minimumWidth = Kirigami.Units.gridUnit * 20;
0310             }
0311             if (windowProperties.minimumHeight === undefined || windowProperties.minimumHeight === null) {
0312                 windowProperties.minimumHeight = Kirigami.Units.gridUnit * 15;
0313             }
0314             if (windowProperties.flags === undefined || windowProperties.flags === null) {
0315                 windowProperties.flags = Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowCloseButtonHint;
0316             }
0317             const windowComponent = Qt.createComponent(Qt.resolvedUrl("./ApplicationWindow.qml"));
0318             const window = windowComponent.createObject(root, windowProperties);
0319             windowComponent.destroy();
0320             item = window.pageStack.push(page, properties);
0321             Object.defineProperty(item, 'closeDialog', {
0322                 value: function() {
0323                     window.close();
0324                 }
0325             });
0326         }
0327         item.Keys.escapePressed.connect(event => item.closeDialog());
0328         return item;
0329     }
0330 
0331     /**
0332      * @brief Inserts a new page or a list of new pages at an arbitrary position.
0333      *
0334      * A single page can be defined as an url, a component, or an object. It can
0335      * also be an array of the above said types, but in that case, the
0336      * properties' array length must match pages' array length or it must be
0337      * empty. Failing to comply with the following rules will make the method
0338      * return null before doing anything.
0339      *
0340      * @param page A single page or an array of pages.
0341      * @param properties A single property object or an array of property
0342      * objects.
0343      *
0344      * @return The new created page (or the last one if it was an array).
0345      * @since 2.7
0346      */
0347     function insertPage(position, page, properties): QT.Page {
0348         if (!pagesLogic.verifyPages(page, properties)) {
0349             console.warn("Inserted pages do not conform to the rules. Please check the documentation.");
0350             console.trace();
0351             return null
0352         }
0353 
0354         if (position < 0 || position > depth) {
0355             console.warn("You are trying to insert a page to an out-of-bounds position. Position will be adjusted accordingly.");
0356             console.trace();
0357             position = Math.max(0, Math.min(depth, position));
0358         }
0359         return pagesLogic.insertPage_unchecked(position, page, properties)
0360     }
0361 
0362     /**
0363      * Move the page at position fromPos to the new position toPos
0364      * If needed, currentIndex will be adjusted
0365      * in order to keep the same current page.
0366      * @since 2.7
0367      */
0368     function movePage(fromPos, toPos): void {
0369         columnView.moveItem(fromPos, toPos);
0370     }
0371 
0372     /**
0373      * @brief Remove the given page.
0374      * @param page The page can be given both as integer position or by reference
0375      * @return The page that has just been removed
0376      * @since 2.7
0377      */
0378     function removePage(page): QT.Page {
0379         if (depth > 0) {
0380             return columnView.removeItem(page);
0381         }
0382         return null
0383     }
0384 
0385     /**
0386      * @brief Pops a page off the stack.
0387      * @param page If page is specified then the stack is unwound to that page,
0388      * to unwind to the first page specify page as null.
0389      * @return The page instance that was popped off the stack.
0390      */
0391     function pop(page): QT.Page {
0392         return columnView.pop(page);
0393     }
0394 
0395     /**
0396      * @brief Replaces a page on the current index.
0397      *
0398      * A single page can be defined as an url, a component, or an object. It can
0399      * also be an array of the above said types, but in that case, the
0400      * properties' array length must match pages' array length or it must be
0401      * empty. Failing to comply with the following rules will make the method
0402      * return null before doing anything.
0403      *
0404      * @param page A single page or an array of pages.
0405      * @param properties A single property object or an array of property
0406      * objects.
0407      *
0408      * @return The new created page (or the last one if it was an array).
0409      * @see push() for details.
0410      */
0411     function replace(page, properties): QT.Page {
0412         if (!pagesLogic.verifyPages(page, properties)) {
0413             console.warn("Specified pages do not conform to the rules. Please check the documentation.");
0414             console.trace();
0415             return null
0416         }
0417 
0418         // Remove all pages on top of the one being replaced.
0419         if (currentIndex >= 0) {
0420             columnView.pop(currentIndex);
0421         } else {
0422             console.warn("There's no page to replace");
0423         }
0424 
0425         // Figure out if more than one page is being pushed.
0426         let pages;
0427         let propsArray = [];
0428         if (page instanceof Array) {
0429             pages = page;
0430             page = pages.shift();
0431         }
0432         if (properties instanceof Array) {
0433             propsArray = properties;
0434             properties = propsArray.shift();
0435         } else {
0436             propsArray = [properties];
0437         }
0438 
0439         // Replace topmost page.
0440         let pageItem = pagesLogic.initPage(page, properties);
0441         if (depth > 0)
0442             columnView.replaceItem(depth - 1, pageItem);
0443         else {
0444             console.log("Calling replace on empty PageRow", pageItem)
0445             columnView.addItem(pageItem)
0446         }
0447         pagePushed(pageItem);
0448 
0449         // Push any extra defined pages onto the stack.
0450         if (pages) {
0451             for (const i in pages) {
0452                 const tPage = pages[i];
0453                 const tProps = propsArray[i];
0454 
0455                 pageItem = pagesLogic.initPage(tPage, tProps);
0456                 columnView.addItem(pageItem);
0457                 pagePushed(pageItem);
0458             }
0459         }
0460 
0461         currentIndex = depth - 1;
0462         return pageItem;
0463     }
0464 
0465     /**
0466      * @brief Clears the page stack.
0467      *
0468      * Destroy (or reparent) all the pages contained.
0469      */
0470     function clear(): void {
0471         columnView.clear();
0472     }
0473 
0474     /**
0475      * @return the page at idx
0476      * @param idx the depth of the page we want
0477      */
0478     function get(idx): QT.Page {
0479         return items[idx];
0480     }
0481 
0482     /**
0483      * Go back to the previous index and scroll to the left to show one more column.
0484      */
0485     function flickBack(): void {
0486         if (depth > 1) {
0487             currentIndex = Math.max(0, currentIndex - 1);
0488         }
0489     }
0490 
0491     /**
0492      * Acts as if you had pressed the "back" button on Android or did Alt-Left on desktop,
0493      * "going back" in the layers and page row. Results in a layer being popped if available,
0494      * or the currentIndex being set to currentIndex-1 if not available.
0495      *
0496      * @param event Optional, an event that will be accepted if a page is successfully
0497      * "backed" on
0498      */
0499     function goBack(event = null): void {
0500         const backEvent = {accepted: false}
0501 
0502         if (layersStack.depth >= 1) {
0503             try { // app code might be screwy, but we still want to continue functioning if it throws an exception
0504                 layersStack.currentItem.backRequested(backEvent)
0505             } catch (error) {}
0506 
0507             if (!backEvent.accepted) {
0508                 if (layersStack.depth > 1) {
0509                     layersStack.pop()
0510                     if (event) {
0511                         event.accepted = true
0512                     }
0513                     return
0514                 }
0515             }
0516         }
0517 
0518         if (currentIndex >= 1) {
0519             try { // app code might be screwy, but we still want to continue functioning if it throws an exception
0520                 currentItem.backRequested(backEvent)
0521             } catch (error) {}
0522 
0523             if (!backEvent.accepted) {
0524                 if (depth > 1) {
0525                     currentIndex = Math.max(0, currentIndex - 1)
0526                     if (event) {
0527                         event.accepted = true
0528                     }
0529                 }
0530             }
0531         }
0532     }
0533 
0534     /**
0535      * Acts as if you had pressed the "forward" shortcut on desktop,
0536      * "going forward" in the page row. Results in the active page
0537      * becoming the next page in the row from the current active page,
0538      * i.e. currentIndex + 1.
0539      */
0540     function goForward(): void {
0541         currentIndex = Math.min(depth - 1, currentIndex + 1)
0542     }
0543 //END FUNCTIONS
0544 
0545 //BEGIN signals & signal handlers
0546     /**
0547      * @brief Emitted when a page has been inserted anywhere.
0548      * @param position where the page has been inserted
0549      * @param page the new page
0550      * @since 2.7
0551      */
0552     signal pageInserted(int position, Item page)
0553 
0554     /**
0555      * @brief Emitted when a page has been pushed to the bottom.
0556      * @param page the new page
0557      * @since 2.5
0558      */
0559     signal pagePushed(Item page)
0560 
0561     /**
0562      * @brief Emitted when a page has been removed from the row.
0563      * @param page the page that has been removed: at this point it's still valid,
0564      *           but may be auto deleted soon.
0565      * @since 2.5
0566      */
0567     signal pageRemoved(Item page)
0568 
0569     onLeftSidebarChanged: {
0570         if (leftSidebar && !leftSidebar.modal) {
0571             modalConnection.onModalChanged();
0572         }
0573     }
0574 
0575     Keys.onReleased: event => {
0576         if (event.key === Qt.Key_Back) {
0577             this.goBack(event)
0578         }
0579     }
0580 
0581     onInitialPageChanged: {
0582         if (initialPage) {
0583             clear();
0584             push(initialPage, null)
0585         }
0586     }
0587 /*
0588     onActiveFocusChanged:  {
0589         if (activeFocus) {
0590             layersStack.currentItem.forceActiveFocus()
0591             if (columnView.activeFocus) {
0592                 print("SSS"+columnView.currentItem)
0593                 columnView.currentItem.forceActiveFocus();
0594             }
0595         }
0596     }
0597 */
0598 //END signals & signal handlers
0599 
0600     Connections {
0601         id: modalConnection
0602         target: leftSidebar
0603         function onModalChanged(): void {
0604             if (leftSidebar.modal) {
0605                 const sidebar = sidebarControl.contentItem;
0606                 const background = sidebarControl.background;
0607                 sidebarControl.contentItem = null;
0608                 leftSidebar.contentItem = sidebar;
0609                 sidebarControl.background = null;
0610                 leftSidebar.background = background;
0611 
0612                 sidebar.visible = true;
0613                 background.visible = true;
0614             } else {
0615                 const sidebar = leftSidebar.contentItem
0616                 const background = leftSidebar.background
0617                 leftSidebar.contentItem=null
0618                 sidebarControl.contentItem = sidebar
0619                 leftSidebar.background=null
0620                 sidebarControl.background = background
0621 
0622                 sidebar.visible = true;
0623                 background.visible = true;
0624             }
0625         }
0626     }
0627 
0628     implicitWidth: contentItem.implicitWidth + leftPadding + rightPadding
0629     implicitHeight: contentItem.implicitHeight + topPadding + bottomPadding
0630 
0631     Shortcut {
0632         sequences: [ StandardKey.Back ]
0633         onActivated: goBack()
0634     }
0635     Shortcut {
0636         sequences: [ StandardKey.Forward ]
0637         onActivated: goForward()
0638     }
0639 
0640     Keys.forwardTo: [currentItem]
0641 
0642     GlobalToolBar.PageRowGlobalToolBarStyleGroup {
0643         id: globalToolBar
0644         readonly property int leftReservedSpace: globalToolBarUI.item ? globalToolBarUI.item.leftReservedSpace : 0
0645         readonly property int rightReservedSpace: globalToolBarUI.item ? globalToolBarUI.item.rightReservedSpace : 0
0646         readonly property int height: globalToolBarUI.height
0647         readonly property Item leftHandleAnchor: globalToolBarUI.item ? globalToolBarUI.item.leftHandleAnchor : null
0648         readonly property Item rightHandleAnchor: globalToolBarUI.item ? globalToolBarUI.item.rightHandleAnchor : null
0649     }
0650 
0651     QQC2.StackView {
0652         id: layerToolbarStack
0653         anchors {
0654             left: parent.left
0655             top: parent.top
0656             right: parent.right
0657         }
0658         //visible: currentItem ? currentItem.implicitHeight > 0 : false
0659         z: 100
0660         height: currentItem?.implicitHeight ?? 0
0661         initialItem: Item {implicitHeight: 0}
0662 
0663         Component {
0664             id: emptyToolbar
0665             Item {
0666                 implicitHeight: 0
0667             }
0668         }
0669         popEnter: Transition {
0670             OpacityAnimator {
0671                 from: 0
0672                 to: 1
0673                 duration: Kirigami.Units.longDuration
0674                 easing.type: Easing.InOutCubic
0675             }
0676         }
0677         popExit: Transition {
0678             OpacityAnimator {
0679                 from: 1
0680                 to: 0
0681                 duration: Kirigami.Units.longDuration
0682                 easing.type: Easing.InOutCubic
0683             }
0684         }
0685         pushEnter: Transition {
0686             OpacityAnimator {
0687                 from: 0
0688                 to: 1
0689                 duration: Kirigami.Units.longDuration
0690                 easing.type: Easing.InOutCubic
0691             }
0692         }
0693         pushExit: Transition {
0694             OpacityAnimator {
0695                 from: 1
0696                 to: 0
0697                 duration: Kirigami.Units.longDuration
0698                 easing.type: Easing.InOutCubic
0699             }
0700         }
0701         replaceEnter: Transition {
0702             OpacityAnimator {
0703                 from: 0
0704                 to: 1
0705                 duration: Kirigami.Units.longDuration
0706                 easing.type: Easing.InOutCubic
0707             }
0708         }
0709         replaceExit: Transition {
0710             OpacityAnimator {
0711                 from: 1
0712                 to: 0
0713                 duration: Kirigami.Units.longDuration
0714                 easing.type: Easing.InOutCubic
0715             }
0716         }
0717     }
0718     QQC2.StackView {
0719         id: layersStack
0720         z: 99
0721         anchors {
0722             left: parent.left
0723             top: layerToolbarStack.bottom
0724             right: parent.right
0725             bottom: parent.bottom
0726         }
0727         // placeholder as initial item
0728         initialItem: columnViewLayout
0729 
0730         onDepthChanged: {
0731             let item = layersStack.get(depth - 1)
0732 
0733             if (layerToolbarStack.depth > depth) {
0734                 while (layerToolbarStack.depth > depth) {
0735                     layerToolbarStack.pop()
0736                 }
0737             } else if (layerToolbarStack.depth < depth) {
0738                 for (let i = layerToolbarStack.depth; i < depth; ++i) {
0739                     let toolBar = layersStack.get(i).Kirigami.ColumnView.globalHeader
0740                     layerToolbarStack.push(toolBar || emptyToolbar)
0741                 }
0742             }
0743             let toolBarItem = layerToolbarStack.get(layerToolbarStack.depth - 1)
0744             if (item.Kirigami.ColumnView.globalHeader != toolBarItem) {
0745                 let toolBar = item.Kirigami.ColumnView.globalHeader
0746                 layerToolbarStack.replace(toolBar || emptyToolbar)
0747             }
0748             // WORKAROUND: the second time the transition on opacity doesn't seem to be executed
0749             toolBarItem = layerToolbarStack.get(layerToolbarStack.depth - 1)
0750             toolBarItem.opacity = 1;
0751         }
0752 
0753         function clear(): void {
0754             // don't let it kill the main page row
0755             const d = layersStack.depth;
0756             for (let i = 1; i < d; ++i) {
0757                 pop();
0758             }
0759         }
0760 
0761         popEnter: Transition {
0762             OpacityAnimator {
0763                 from: 0
0764                 to: 1
0765                 duration: Kirigami.Units.longDuration
0766                 easing.type: Easing.InOutCubic
0767             }
0768         }
0769         popExit: Transition {
0770             ParallelAnimation {
0771                 OpacityAnimator {
0772                     from: 1
0773                     to: 0
0774                     duration: Kirigami.Units.longDuration
0775                     easing.type: Easing.InOutCubic
0776                 }
0777                 YAnimator {
0778                     from: 0
0779                     to: height/2
0780                     duration: Kirigami.Units.longDuration
0781                     easing.type: Easing.InCubic
0782                 }
0783             }
0784         }
0785 
0786         pushEnter: Transition {
0787             ParallelAnimation {
0788                 // NOTE: It's a PropertyAnimation instead of an Animator because with an animator the item will be visible for an instant before starting to fade
0789                 PropertyAnimation {
0790                     property: "opacity"
0791                     from: 0
0792                     to: 1
0793                     duration: Kirigami.Units.longDuration
0794                     easing.type: Easing.InOutCubic
0795                 }
0796                 YAnimator {
0797                     from: height/2
0798                     to: 0
0799                     duration: Kirigami.Units.longDuration
0800                     easing.type: Easing.OutCubic
0801                 }
0802             }
0803         }
0804 
0805 
0806         pushExit: Transition {
0807             OpacityAnimator {
0808                 from: 1
0809                 to: 0
0810                 duration: Kirigami.Units.longDuration
0811                 easing.type: Easing.InOutCubic
0812             }
0813         }
0814 
0815         replaceEnter: Transition {
0816             ParallelAnimation {
0817                 OpacityAnimator {
0818                     from: 0
0819                     to: 1
0820                     duration: Kirigami.Units.longDuration
0821                     easing.type: Easing.InOutCubic
0822                 }
0823                 YAnimator {
0824                     from: height/2
0825                     to: 0
0826                     duration: Kirigami.Units.longDuration
0827                     easing.type: Easing.OutCubic
0828                 }
0829             }
0830         }
0831 
0832         replaceExit: Transition {
0833             ParallelAnimation {
0834                 OpacityAnimator {
0835                     from: 1
0836                     to: 0
0837                     duration: Kirigami.Units.longDuration
0838                     easing.type: Easing.InCubic
0839                 }
0840                 YAnimator {
0841                     from: 0
0842                     to: -height/2
0843                     duration: Kirigami.Units.longDuration
0844                     easing.type: Easing.InOutCubic
0845                 }
0846             }
0847         }
0848     }
0849 
0850     Loader {
0851         id: globalToolBarUI
0852         anchors {
0853             left: parent.left
0854             top: parent.top
0855             right: parent.right
0856         }
0857         z: 100
0858         property QT.Control pageRow: root
0859         active: (!leadingVisibleItem || leadingVisibleItem.globalToolBarStyle !== Kirigami.ApplicationHeaderStyle.None) &&
0860                 (globalToolBar.actualStyle !== Kirigami.ApplicationHeaderStyle.None || (leadingVisibleItem && leadingVisibleItem.globalToolBarStyle === Kirigami.ApplicationHeaderStyle.ToolBar))
0861         visible: active
0862         height: active ? implicitHeight : 0
0863         // If load is asynchronous, it will fail to compute the initial implicitHeight
0864         // https://bugs.kde.org/show_bug.cgi?id=442660
0865         asynchronous: false
0866         source: Qt.resolvedUrl("private/globaltoolbar/PageRowGlobalToolBarUI.qml");
0867     }
0868 
0869     QtObject {
0870         id: pagesLogic
0871         readonly property var componentCache: new Array()
0872 
0873         property Component __mobileDialogLayerComponent
0874 
0875         function getMobileDialogLayerComponent() {
0876             if (!__mobileDialogLayerComponent) {
0877                 __mobileDialogLayerComponent = Qt.createComponent(Qt.resolvedUrl("private/MobileDialogLayer.qml"));
0878             }
0879             return __mobileDialogLayerComponent;
0880         }
0881 
0882         function verifyPages(pages, properties): bool {
0883             function validPage(page) {
0884                 //don't try adding an already existing page
0885                 if (page instanceof QT.Page && columnView.containsItem(page)) {
0886                     console.log(`Page ${page} is already in the PageRow`)
0887                     return false
0888                 }
0889                 return page instanceof QT.Page || page instanceof Component || typeof page === 'string'
0890                     || (typeof page === 'object' && typeof page.toString() === 'string')
0891             }
0892 
0893             // check page/pages that it is/they are valid
0894             const pagesIsArr = Array.isArray(pages) && pages.length > 0
0895             let isValidArrOfPages = pagesIsArr;
0896 
0897             if (pagesIsArr) {
0898                 for (const page of pages) {
0899                     if (!validPage(page)) {
0900                         isValidArrOfPages = false;
0901                         break;
0902                     }
0903                 }
0904             }
0905 
0906             // check properties obejct/array object validity
0907             const isProp = typeof properties === 'object';
0908             const propsIsArr = Array.isArray(properties) && properties.length > 0
0909             let isValidPropArr = propsIsArr;
0910 
0911             if (propsIsArr) {
0912                 for (const prop of properties) {
0913                     if (typeof prop !== 'object') {
0914                         isValidPropArr = false;
0915                         break;
0916                     }
0917                 }
0918                 isValidPropArr = isValidPropArr && pages.length === properties.length
0919             }
0920 
0921             return (validPage(pages) || isValidArrOfPages)
0922                 && (!properties || (isProp || isValidPropArr))
0923         }
0924 
0925         function insertPage_unchecked(position, page, properties) {
0926             columnView.pop(position - 1);
0927 
0928             // figure out if more than one page is being pushed
0929             let pages;
0930             let propsArray = [];
0931             if (page instanceof Array) {
0932                 pages = page;
0933                 page = pages.pop();
0934             }
0935             if (properties instanceof Array) {
0936                 propsArray = properties;
0937                 properties = propsArray.pop();
0938             } else {
0939                 propsArray = [properties];
0940             }
0941 
0942             // push any extra defined pages onto the stack
0943             if (pages) {
0944                 for (const i in pages) {
0945                     let tPage = pages[i];
0946                     let tProps = propsArray[i];
0947 
0948                     pagesLogic.initAndInsertPage(position, tPage, tProps);
0949                     ++position;
0950                 }
0951             }
0952 
0953             // initialize the page
0954             const pageItem = pagesLogic.initAndInsertPage(position, page, properties);
0955 
0956             pagePushed(pageItem);
0957 
0958             return pageItem;
0959         }
0960 
0961         function getPageComponent(page): Component {
0962             let pageComp;
0963 
0964             if (page.createObject) {
0965                 // page defined as component
0966                 pageComp = page;
0967             } else if (typeof page === "string") {
0968                 // page defined as string (a url)
0969                 pageComp = pagesLogic.componentCache[page];
0970                 if (!pageComp) {
0971                     pageComp = pagesLogic.componentCache[page] = Qt.createComponent(page);
0972                 }
0973             } else if (typeof page === "object" && !(page instanceof Item) && page.toString !== undefined) {
0974                 // page defined as url (QML value type, not a string)
0975                 pageComp = pagesLogic.componentCache[page.toString()];
0976                 if (!pageComp) {
0977                     pageComp = pagesLogic.componentCache[page.toString()] = Qt.createComponent(page.toString());
0978                 }
0979             }
0980 
0981             return pageComp
0982         }
0983 
0984         function initPage(page, properties): QT.Page {
0985             const pageComp = getPageComponent(page, properties);
0986 
0987             if (pageComp) {
0988                 // instantiate page from component
0989                 // FIXME: parent directly to columnView or root?
0990                 page = pageComp.createObject(null, properties || {});
0991 
0992                 if (pageComp.status === Component.Error) {
0993                     throw new Error("Error while loading page: " + pageComp.errorString());
0994                 }
0995             } else {
0996                 // copy properties to the page
0997                 for (const prop in properties) {
0998                     if (properties.hasOwnProperty(prop)) {
0999                         page[prop] = properties[prop];
1000                     }
1001                 }
1002             }
1003             return page;
1004         }
1005 
1006         function initAndInsertPage(position, page, properties): QT.Page {
1007             page = initPage(page, properties);
1008             columnView.insertItem(position, page);
1009             return page;
1010         }
1011     }
1012 
1013     RowLayout {
1014         id: columnViewLayout
1015         spacing: 1
1016         readonly property alias columnView: columnView
1017         anchors {
1018             fill: parent
1019             topMargin: -layersStack.y
1020         }
1021         QQC2.Control {
1022             id: sidebarControl
1023             Layout.fillHeight: true
1024             visible: contentItem !== null
1025             leftPadding: root.leftSidebar ? root.leftSidebar.leftPadding : 0
1026             topPadding: root.leftSidebar ? root.leftSidebar.topPadding : 0
1027             rightPadding: root.leftSidebar ? root.leftSidebar.rightPadding : 0
1028             bottomPadding: root.leftSidebar ? root.leftSidebar.bottomPadding : 0
1029         }
1030         Kirigami.ColumnView {
1031             id: columnView
1032             Layout.fillWidth: true
1033             Layout.fillHeight: true
1034 
1035             topPadding: globalToolBarUI.item && globalToolBarUI.item.breadcrumbVisible
1036                         ? globalToolBarUI.height : 0
1037 
1038             // Internal hidden api for Page
1039             readonly property Item __pageRow: root
1040             acceptsMouse: Kirigami.Settings.isMobile
1041             columnResizeMode: root.wideMode ? Kirigami.ColumnView.FixedColumns : Kirigami.ColumnView.SingleColumn
1042             columnWidth: root.defaultColumnWidth
1043 
1044             onItemInserted: (position, item) => root.pageInserted(position, item);
1045             onItemRemoved: item => root.pageRemoved(item);
1046 
1047             onVisibleItemsChanged: {
1048                 // implementation of `popHiddenPages` option
1049                 if (root.popHiddenPages) {
1050                     // manually fetch lastItem here rather than use root.lastItem property, since that binding may not have updated yet
1051                     let lastItem = columnView.contentChildren[columnView.contentChildren.length - 1];
1052                     let trailingVisibleItem = columnView.trailingVisibleItem;
1053 
1054                     // pop every page that isn't visible and at the top of the stack
1055                     while (lastItem && columnView.trailingVisibleItem &&
1056                         lastItem !== columnView.trailingVisibleItem && columnView.containsItem(lastItem)) {
1057                         root.pop();
1058                     }
1059                 }
1060             }
1061         }
1062     }
1063 
1064     Rectangle {
1065         anchors.bottom: parent.bottom
1066         height: Kirigami.Units.smallSpacing
1067         x: (columnView.width - width) * (columnView.contentX / (columnView.contentWidth - columnView.width))
1068         width: columnView.width * (columnView.width/columnView.contentWidth)
1069         color: Kirigami.Theme.textColor
1070         opacity: 0
1071         onXChanged: {
1072             opacity = 0.3
1073             scrollIndicatorTimer.restart();
1074         }
1075         Behavior on opacity {
1076             OpacityAnimator {
1077                 duration: Kirigami.Units.longDuration
1078                 easing.type: Easing.InOutQuad
1079             }
1080         }
1081         Timer {
1082             id: scrollIndicatorTimer
1083             interval: Kirigami.Units.longDuration * 4
1084             onTriggered: parent.opacity = 0;
1085         }
1086     }
1087 }