Kirigami2

PageRow.qml
1/*
2 * SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
3 *
4 * SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6
7import QtQuick
8import QtQuick.Layouts
9import QtQuick.Templates as QT
10import QtQuick.Controls as QQC2
11import org.kde.kirigami as Kirigami
12import "private/globaltoolbar" as GlobalToolBar
13import "templates" as KT
14
15/**
16 * PageRow implements a row-based navigation model, which can be used
17 * with a set of interlinked information pages. Pages are pushed in the
18 * back of the row and the view scrolls until that row is visualized.
19 * A PageRow can show a single page or a multiple set of columns, depending
20 * on the window width: on a phone a single column should be fullscreen,
21 * while on a tablet or a desktop more than one column should be visible.
22 *
23 * @inherits QtQuick.Templates.Control
24 */
25QT.Control {
26 id: root
27
28//BEGIN PROPERTIES
29 /**
30 * @brief This property holds the number of pages currently pushed onto the view.
31 * @property int depth
32 */
33 readonly property alias depth: columnView.count
34
35 /**
36 * @brief This property holds the last page in the row.
37 * @property Page lastItem
38 */
39 readonly property Item lastItem: columnView.contentChildren.length > 0 ? columnView.contentChildren[columnView.contentChildren.length - 1] : null
40
41 /**
42 * @brief This property holds the currently visible/active page.
43 *
44 * Because of the ability to display multiple pages, it will hold the currently active page.
45 *
46 * @property Page currentItem
47 */
48 readonly property alias currentItem: columnView.currentItem
49
50 /**
51 * @brief This property holds the index of the currently active page.
52 * @see currentItem
53 * @property int currentIndex
54 */
55 property alias currentIndex: columnView.currentIndex
56
57 /**
58 * @brief This property sets the initial page for this PageRow.
59 * @property Page initialPage
60 */
61 property var initialPage
62
63 /**
64 * @brief This property holds the main ColumnView of this Row.
65 * @property ColumnView contentItem
66 */
67 contentItem: columnView
68
69 /**
70 * @brief This property holds the ColumnView that this PageRow owns.
71 *
72 * Generally, you shouldn't need to change the value of this property.
73 *
74 * @property ColumnView columnView
75 * @since 2.12
76 */
77 property alias columnView: columnView
79 /**
80 * @brief This property holds the present pages in the PageRow.
81 * @property list<Page> items
82 * @since 2.6
83 */
84 readonly property alias items: columnView.contentChildren
85
86 /**
87 * @brief This property holds all visible pages in the PageRow,
88 * excluding those which are scrolled away.
89 * @property list<Page> visibleItems
90 * @since 2.6
91 */
92 readonly property alias visibleItems: columnView.visibleItems
93
94 /**
95 * @brief This property holds the first page in the PageRow that is at least partially visible.
96 * @note Pages before that one (the one contained in the property) will be out of the viewport.
97 * @see ColumnView::leadingVisibleItem
98 * @property Item leadingVisibleItem
99 * @since 2.6
100 */
101 readonly property alias leadingVisibleItem: columnView.leadingVisibleItem
102
103 /**
104 * @brief This property holds the last page in the PageRow that is at least partially visible.
105 * @note Pages after that one (the one contained in the property) will be out of the viewport.
106 * @see ColumnView::trailingVisibleItem
107 * @property Item trailingVisibleItem
108 * @since 2.6
109 */
110 readonly property alias trailingVisibleItem: columnView.trailingVisibleItem
111
112 /**
113 * @brief This property holds the default width for a column.
114 *
115 * default: ``20 * Kirigami.Units.gridUnit``
116 *
117 * @note Pages can override it using implicitWidth, Layout.fillWidth, Layout.minimumWidth etc.
118 */
119 property int defaultColumnWidth: Kirigami.Units.gridUnit * 20
120
121 /**
122 * @brief This property sets whether it is possible to go back/forward
123 * by swiping with a gesture on the content view.
124 *
125 * default: ``true``
126 *
127 * @property bool interactive
128 */
129 property alias interactive: columnView.interactive
130
131 /**
132 * @brief This property tells whether the PageRow is wide enough to show multiple pages.
133 * @since 5.37
134 */
135 readonly property bool wideMode: width >= defaultColumnWidth * 2 && depth >= 2
137 /**
138 * @brief This property sets whether the separators between pages should be displayed.
139 *
140 * default: ``true``
141 *
142 * @property bool separatorVisible
143 * @since 5.38
144 */
145 property alias separatorVisible: columnView.separatorVisible
146
147 /**
148 * @brief This property sets the appearance of an optional global toolbar for the whole PageRow.
149 *
150 * It's a grouped property comprised of the following properties:
151 * * style (``Kirigami.ApplicationHeaderStyle``): can have the following values:
152 * * ``Auto``: Depending on application formfactor, it can behave automatically like other values, such as a Breadcrumb on mobile and ToolBar on desktop.
153 * * ``Breadcrumb``: It will show a breadcrumb of all the page titles in the stack, for easy navigation.
154 * * ``Titles``: Each page will only have its own title on top.
155 * * ``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.
156 * * ``None``: No global toolbar will be shown.
157 *
158 * * ``actualStyle``: This will represent the actual style of the toolbar; it can be different from style in the case style is Auto.
159 * * ``showNavigationButtons``: OR flags combination of Kirigami.ApplicationHeaderStyle.ShowBackButton and Kirigami.ApplicationHeaderStyle.ShowForwardButton.
160 * * ``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``).
161 * * ``minimumHeight: int`` Minimum height of the header, which will be resized when scrolling. Only in Mobile mode (default: ``preferredHeight``, sliding but no scaling).
162 * * ``preferredHeight: int`` The height the toolbar will usually have.
163 * * ``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).
164 * * ``rightReservedSpace: int, readonly`` How many pixels of extra space are reserved at the right of the page toolbar (typically for a drawer handle).
165 *
166 * @property org::kde::kirigami::private::globaltoolbar::PageRowGlobalToolBarStyleGroup globalToolBar
167 * @since 5.48
168 */
169 readonly property alias globalToolBar: globalToolBar
171 /**
172 * @brief This property assigns a drawer as an internal left sidebar for this PageRow.
173 *
174 * In this case, when open and not modal, the drawer contents will be in the same layer as the base pagerow.
175 * Pushing any other layer on top will cover the sidebar.
176 *
177 * @since 5.84
178 */
179 // TODO KF6: globaldrawer should use actions also used by this sidebar instead of reparenting globaldrawer contents?
180 property OverlayDrawer leftSidebar
181
182 /**
183 * @brief This property holds the modal layers.
184 *
185 * Sometimes an application needs a modal page that always covers all the rows.
186 * For instance the full screen image of an image viewer or a settings page.
187 *
188 * @property QtQuick.Controls.StackView layers
189 * @since 5.38
190 */
191 property alias layers: layersStack
192
193 /**
194 * @brief This property holds whether to automatically pop pages at the top of the stack if they are not visible.
195 *
196 * If a user navigates to a previous page on the stack (ex. pressing back button) and pages above
197 * it on the stack are not visible, they will be popped if this property is true.
198 *
199 * @since 5.101
200 */
201 property bool popHiddenPages: false
202//END PROPERTIES
203
204//BEGIN FUNCTIONS
205 /**
206 * @brief This method pushes a page on the stack.
207 *
208 * A single page can be defined as an url, a component, or an object. It can
209 * also be an array of the above said types, but in that case, the
210 * properties' array length must match pages' array length or it must be
211 * empty. Failing to comply with the following rules will make the method
212 * return null before doing anything.
213 *
214 * @param page A single page or an array of pages.
215 * @param properties A single property object or an array of property
216 * objects.
217 *
218 * @return The new created page (or the last one if it was an array).
219 */
220 function push(page, properties): QT.Page {
221 if (!pagesLogic.verifyPages(page, properties)) {
222 console.warn("Pushed pages do not conform to the rules. Please check the documentation.");
223 console.trace();
224 return null
225 }
226
227 const item = pagesLogic.insertPage_unchecked(currentIndex + 1, page, properties)
228 currentIndex = depth - 1
229 return item
230 }
231
232 /**
233 * @brief Pushes a page as a new dialog on desktop and as a layer on mobile.
234 *
235 * @param page A single page defined as either a string url, a component or
236 * an object (which will be reparented). The following page gains
237 * `closeDialog()` method allowing to make it indistinguishable to
238 * close/hide it when in desktop or mobile mode. Note that Kiriami supports
239 * calling `closeDialog()` only once.
240 *
241 * @param properties The properties given when initializing the page.
242 * @param windowProperties The properties given to the initialized window on desktop.
243 * @return Returns a newly created page.
244 */
245 function pushDialogLayer(page, properties = {}, windowProperties = {}): QT.Page {
246 if (!pagesLogic.verifyPages(page, properties)) {
247 console.warn("Page pushed as a dialog or layer does not conform to the rules. Please check the documentation.");
248 console.trace();
249 return null
250 }
251 let item;
252 if (Kirigami.Settings.isMobile) {
253 if (QQC2.ApplicationWindow.window.width > Kirigami.Units.gridUnit * 40) {
254 // open as a QQC2.Dialog
255 const component = pagesLogic.getMobileDialogLayerComponent();
256 const dialog = component.createObject(QQC2.Overlay.overlay, {
257 width: Qt.binding(() => QQC2.ApplicationWindow.window.width - Kirigami.Units.gridUnit * 5),
258 height: Qt.binding(() => QQC2.ApplicationWindow.window.height - Kirigami.Units.gridUnit * 5),
259 x: Kirigami.Units.gridUnit * 2.5,
260 y: Kirigami.Units.gridUnit * 2.5,
261 });
262
263 if (typeof page === "string") {
264 // url => load component and then load item from component
265 const component = Qt.createComponent(Qt.resolvedUrl(page));
266 item = component.createObject(dialog.contentItem, properties);
267 component.destroy();
268 dialog.contentItem.contentItem = item
269 } else if (page instanceof Component) {
270 item = page.createObject(dialog.contentItem, properties);
271 dialog.contentItem.contentItem = item
272 } else if (page instanceof Item) {
273 item = page;
274 page.parent = dialog.contentItem;
275 } else if (typeof page === 'object' && typeof page.toString() === 'string') { // url
276 const component = Qt.createComponent(page);
277 item = component.createObject(dialog.contentItem, properties);
278 component.destroy();
279 dialog.contentItem.contentItem = item
280 }
281 dialog.title = Qt.binding(() => item.title);
282
283 // Pushing a PageRow is supported but without PageRow toolbar
284 if (item.globalToolBar && item.globalToolBar.style) {
285 item.globalToolBar.style = Kirigami.ApplicationHeaderStyle.None
286 }
287 Object.defineProperty(item, 'closeDialog', {
288 value: function() {
289 dialog.close();
290 }
291 });
292 dialog.open();
293 } else {
294 // open as a layer
295 properties.globalToolBarStyle = root.globalToolBar.style
296 item = layers.push(page, properties);
297 Object.defineProperty(item, 'closeDialog', {
298 value: function() {
299 layers.pop();
300 }
301 });
302 }
303 } else {
304 // open as a new window
305 if (!("modality" in windowProperties)) {
306 windowProperties.modality = Qt.WindowModal;
307 }
308 if (!("height" in windowProperties)) {
309 windowProperties.height = Kirigami.Units.gridUnit * 30;
310 }
311 if (!("width" in windowProperties)) {
312 windowProperties.width = Kirigami.Units.gridUnit * 50;
313 }
314 if (!("minimumWidth" in windowProperties)) {
315 windowProperties.minimumWidth = Kirigami.Units.gridUnit * 20;
316 }
317 if (!("minimumHeight" in windowProperties)) {
318 windowProperties.minimumHeight = Kirigami.Units.gridUnit * 15;
319 }
320 if (!("flags" in windowProperties)) {
321 windowProperties.flags = Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowCloseButtonHint;
322 }
323 const windowComponent = Qt.createComponent(Qt.resolvedUrl("./ApplicationWindow.qml"));
324 const window = windowComponent.createObject(root, windowProperties);
325 windowComponent.destroy();
326 item = window.pageStack.push(page, properties);
327 Object.defineProperty(item, 'closeDialog', {
328 value: function() {
329 window.close();
330 }
331 });
332 }
333 item.Keys.escapePressed.connect(event => item.closeDialog());
334 return item;
335 }
336
337 /**
338 * @brief Inserts a new page or a list of new pages at an arbitrary position.
339 *
340 * A single page can be defined as an url, a component, or an object. It can
341 * also be an array of the above said types, but in that case, the
342 * properties' array length must match pages' array length or it must be
343 * empty. Failing to comply with the following rules will make the method
344 * return null before doing anything.
345 *
346 * @param page A single page or an array of pages.
347 * @param properties A single property object or an array of property
348 * objects.
349 *
350 * @return The new created page (or the last one if it was an array).
351 * @since 2.7
352 */
353 function insertPage(position, page, properties): QT.Page {
354 if (!pagesLogic.verifyPages(page, properties)) {
355 console.warn("Inserted pages do not conform to the rules. Please check the documentation.");
356 console.trace();
357 return null
358 }
359
360 if (position < 0 || position > depth) {
361 console.warn("You are trying to insert a page to an out-of-bounds position. Position will be adjusted accordingly.");
362 console.trace();
363 position = Math.max(0, Math.min(depth, position));
364 }
365 return pagesLogic.insertPage_unchecked(position, page, properties)
366 }
367
368 /**
369 * Move the page at position fromPos to the new position toPos
370 * If needed, currentIndex will be adjusted
371 * in order to keep the same current page.
372 * @since 2.7
373 */
374 function movePage(fromPos, toPos): void {
375 columnView.moveItem(fromPos, toPos);
376 }
377
378 /**
379 * @brief Remove the given page.
380 * @param page The page can be given both as integer position or by reference
381 * @return The page that has just been removed
382 * @since 2.7
383 */
384 function removePage(page): QT.Page {
385 if (depth > 0) {
386 return columnView.removeItem(page);
387 }
388 return null
389 }
390
391 /**
392 * @brief Pops a page off the stack.
393 * @param page If page is specified then the stack is unwound to that page,
394 * to unwind to the first page specify page as null.
395 * @return The page instance that was popped off the stack.
396 */
397 function pop(page): QT.Page {
398 return columnView.pop(page);
399 }
400
401 /**
402 * @brief Replaces a page on the current index.
403 *
404 * A single page can be defined as an url, a component, or an object. It can
405 * also be an array of the above said types, but in that case, the
406 * properties' array length must match pages' array length or it must be
407 * empty. Failing to comply with the following rules will make the method
408 * return null before doing anything.
409 *
410 * @param page A single page or an array of pages.
411 * @param properties A single property object or an array of property
412 * objects.
413 *
414 * @return The new created page (or the last one if it was an array).
415 * @see push() for details.
416 */
417 function replace(page, properties): QT.Page {
418 if (!pagesLogic.verifyPages(page, properties)) {
419 console.warn("Specified pages do not conform to the rules. Please check the documentation.");
420 console.trace();
421 return null
422 }
423
424 // Remove all pages on top of the one being replaced.
425 if (currentIndex >= 0) {
426 columnView.pop(currentIndex);
427 } else {
428 console.warn("There's no page to replace");
429 }
430
431 // Figure out if more than one page is being pushed.
432 let pages;
433 let propsArray = [];
434 if (page instanceof Array) {
435 pages = page;
436 page = pages.shift();
437 }
438 if (properties instanceof Array) {
439 propsArray = properties;
440 properties = propsArray.shift();
441 } else {
442 propsArray = [properties];
443 }
444
445 // Replace topmost page.
446 let pageItem = pagesLogic.initPage(page, properties);
447 if (depth > 0)
448 columnView.replaceItem(depth - 1, pageItem);
449 else {
450 console.log("Calling replace on empty PageRow", pageItem)
451 columnView.addItem(pageItem)
452 }
453 pagePushed(pageItem);
454
455 // Push any extra defined pages onto the stack.
456 if (pages) {
457 for (const i in pages) {
458 const tPage = pages[i];
459 const tProps = propsArray[i];
460
461 pageItem = pagesLogic.initPage(tPage, tProps);
462 columnView.addItem(pageItem);
463 pagePushed(pageItem);
464 }
465 }
466
467 currentIndex = depth - 1;
468 return pageItem;
469 }
470
471 /**
472 * @brief Clears the page stack.
473 *
474 * Destroy (or reparent) all the pages contained.
475 */
476 function clear(): void {
477 columnView.clear();
478 }
479
480 /**
481 * @return the page at idx
482 * @param idx the depth of the page we want
483 */
484 function get(idx): QT.Page {
485 return items[idx];
486 }
487
488 /**
489 * Go back to the previous index and scroll to the left to show one more column.
490 */
491 function flickBack(): void {
492 if (depth > 1) {
493 currentIndex = Math.max(0, currentIndex - 1);
494 }
495 }
496
497 /**
498 * Acts as if you had pressed the "back" button on Android or did Alt-Left on desktop,
499 * "going back" in the layers and page row. Results in a layer being popped if available,
500 * or the currentIndex being set to currentIndex-1 if not available.
501 *
502 * @param event Optional, an event that will be accepted if a page is successfully
503 * "backed" on
504 */
505 function goBack(event = null): void {
506 const backEvent = {accepted: false}
507
508 if (layersStack.depth >= 1) {
509 try { // app code might be screwy, but we still want to continue functioning if it throws an exception
510 layersStack.currentItem.backRequested(backEvent)
511 } catch (error) {}
512
513 if (!backEvent.accepted) {
514 if (layersStack.depth > 1) {
515 layersStack.pop()
516 if (event) {
517 event.accepted = true
518 }
519 return
520 }
521 }
522 }
523
524 if (currentIndex >= 1) {
525 try { // app code might be screwy, but we still want to continue functioning if it throws an exception
526 currentItem.backRequested(backEvent)
527 } catch (error) {}
528
529 if (!backEvent.accepted) {
530 if (depth > 1) {
531 currentIndex = Math.max(0, currentIndex - 1)
532 if (event) {
533 event.accepted = true
534 }
535 }
536 }
537 }
538 }
539
540 /**
541 * Acts as if you had pressed the "forward" shortcut on desktop,
542 * "going forward" in the page row. Results in the active page
543 * becoming the next page in the row from the current active page,
544 * i.e. currentIndex + 1.
545 */
546 function goForward(): void {
547 currentIndex = Math.min(depth - 1, currentIndex + 1)
548 }
549//END FUNCTIONS
550
551//BEGIN signals & signal handlers
552 /**
553 * @brief Emitted when a page has been inserted anywhere.
554 * @param position where the page has been inserted
555 * @param page the new page
556 * @since 2.7
557 */
558 signal pageInserted(int position, Item page)
559
560 /**
561 * @brief Emitted when a page has been pushed to the bottom.
562 * @param page the new page
563 * @since 2.5
564 */
565 signal pagePushed(Item page)
566
567 /**
568 * @brief Emitted when a page has been removed from the row.
569 * @param page the page that has been removed: at this point it's still valid,
570 * but may be auto deleted soon.
571 * @since 2.5
572 */
573 signal pageRemoved(Item page)
574
575 onLeftSidebarChanged: {
576 if (leftSidebar && !leftSidebar.modal) {
577 modalConnection.onModalChanged();
578 }
579 }
580
581 Keys.onReleased: event => {
582 if (event.key === Qt.Key_Back) {
583 this.goBack(event)
584 }
585 }
586
587 onInitialPageChanged: {
588 if (initialPage) {
589 clear();
590 push(initialPage, null)
591 }
592 }
593/*
594 onActiveFocusChanged: {
595 if (activeFocus) {
596 layersStack.currentItem.forceActiveFocus()
597 if (columnView.activeFocus) {
598 print("SSS"+columnView.currentItem)
599 columnView.currentItem.forceActiveFocus();
600 }
601 }
602 }
603*/
604//END signals & signal handlers
605
606 Connections {
607 id: modalConnection
608 target: leftSidebar
609 function onModalChanged(): void {
610 if (leftSidebar.modal) {
611 const sidebar = sidebarControl.contentItem;
612 const background = sidebarControl.background;
613 sidebarControl.contentItem = null;
614 leftSidebar.contentItem = sidebar;
615 sidebarControl.background = null;
616 leftSidebar.background = background;
617
618 sidebar.visible = true;
619 background.visible = true;
620 } else {
621 const sidebar = leftSidebar.contentItem
622 const background = leftSidebar.background
623 leftSidebar.contentItem=null
624 sidebarControl.contentItem = sidebar
625 leftSidebar.background=null
626 sidebarControl.background = background
627
628 sidebar.visible = true;
629 background.visible = true;
630 }
631 }
632 }
633
634 implicitWidth: contentItem.implicitWidth + leftPadding + rightPadding
635 implicitHeight: contentItem.implicitHeight + topPadding + bottomPadding
636
637 Shortcut {
638 sequences: [ StandardKey.Back ]
639 onActivated: goBack()
640 }
641 Shortcut {
642 sequences: [ StandardKey.Forward ]
643 onActivated: goForward()
644 }
645
646 Keys.forwardTo: [currentItem]
647
648 GlobalToolBar.PageRowGlobalToolBarStyleGroup {
649 id: globalToolBar
650 readonly property int leftReservedSpace: globalToolBarUI.item ? globalToolBarUI.item.leftReservedSpace : 0
651 readonly property int rightReservedSpace: globalToolBarUI.item ? globalToolBarUI.item.rightReservedSpace : 0
652 readonly property int height: globalToolBarUI.height
653 readonly property Item leftHandleAnchor: globalToolBarUI.item ? globalToolBarUI.item.leftHandleAnchor : null
654 readonly property Item rightHandleAnchor: globalToolBarUI.item ? globalToolBarUI.item.rightHandleAnchor : null
655 }
656
657 QQC2.StackView {
658 id: layersStack
659 z: 99
660 anchors {
661 left: parent.left
662 top: parent.top
663 right: parent.right
664 bottom: parent.bottom
665 topMargin: depth < 2
666 ? globalToolBarUI.height
667 : currentItem.Kirigami.ColumnView.globalHeader?.height ?? 0
668 bottomMargin: currentItem.Kirigami.ColumnView.globalFooter?.height ?? 0
669 }
670 // placeholder as initial item
671 initialItem: columnViewLayout
672
673 onDepthChanged: {
674 if (depth < 2) {
675 return;
676 }
677 let item = layersStack.get(depth - 1)
678 // headers are parented to items in layers, so items can't clip otherwise headers become invisible
679 item.clip = false
680
681 // For layers reparent the global header to the page
682 const header = item.Kirigami.ColumnView.globalHeader
683 header.parent = item
684 header.anchors.bottom = item.top
685 header.anchors.left = item.left
686 header.anchors.right = item.right
687
688 const footer = item.Kirigami.ColumnView.globalFooter
689 footer.parent = item
690 footer.anchors.top = item.bottom
691 footer.anchors.left = item.left
692 footer.anchors.right = item.right
693 }
694
695 function clear(): void {
696 // don't let it kill the main page row
697 const d = layersStack.depth;
698 for (let i = 1; i < d; ++i) {
699 pop();
700 }
701 }
702
703 popEnter: Transition {
704 PauseAnimation {
705 duration: Kirigami.Units.longDuration
706 }
707 }
708 popExit: Transition {
709 ParallelAnimation {
710 OpacityAnimator {
711 from: 1
712 to: 0
713 duration: Kirigami.Units.longDuration
714 easing.type: Easing.InOutCubic
715 }
716 YAnimator {
717 from: 0
718 to: height/2
719 duration: Kirigami.Units.longDuration
720 easing.type: Easing.InCubic
721 }
722 }
723 }
724
725 pushEnter: Transition {
726 ParallelAnimation {
727 // 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
728 PropertyAnimation {
729 property: "opacity"
730 from: 0
731 to: 1
732 duration: Kirigami.Units.longDuration
733 easing.type: Easing.InOutCubic
734 }
735 YAnimator {
736 from: height/2
737 to: 0
738 duration: Kirigami.Units.longDuration
739 easing.type: Easing.OutCubic
740 }
741 }
742 }
743
744
745
746 pushExit: Transition {
747 PauseAnimation {
748 duration: Kirigami.Units.longDuration
749 }
750 }
751
752 replaceEnter: Transition {
753 ParallelAnimation {
754 OpacityAnimator {
755 from: 0
756 to: 1
757 duration: Kirigami.Units.longDuration
758 easing.type: Easing.InOutCubic
759 }
760 YAnimator {
761 from: height/2
762 to: 0
763 duration: Kirigami.Units.longDuration
764 easing.type: Easing.OutCubic
765 }
766 }
767 }
768
769 replaceExit: Transition {
770 ParallelAnimation {
771 OpacityAnimator {
772 from: 1
773 to: 0
774 duration: Kirigami.Units.longDuration
775 easing.type: Easing.InCubic
776 }
777 YAnimator {
778 from: 0
779 to: -height/2
780 duration: Kirigami.Units.longDuration
781 easing.type: Easing.InOutCubic
782 }
783 }
784 }
785 }
786
787 Loader {
788 id: globalToolBarUI
789 anchors {
790 left: parent.left
791 top: parent.top
792 right: parent.right
793 }
794 z: 100
795 property QT.Control pageRow: root
796 active: globalToolBar.actualStyle !== Kirigami.ApplicationHeaderStyle.None || (leadingVisibleItem && leadingVisibleItem.globalToolBarStyle === Kirigami.ApplicationHeaderStyle.ToolBar)
797 visible: active
798 height: active ? implicitHeight : 0
799 // If load is asynchronous, it will fail to compute the initial implicitHeight
800 // https://bugs.kde.org/show_bug.cgi?id=442660
801 asynchronous: false
802 source: Qt.resolvedUrl("private/globaltoolbar/PageRowGlobalToolBarUI.qml");
803 }
804
805 QtObject {
806 id: pagesLogic
807 readonly property var componentCache: new Array()
808
809 property Component __mobileDialogLayerComponent
810
811 function getMobileDialogLayerComponent() {
812 if (!__mobileDialogLayerComponent) {
813 __mobileDialogLayerComponent = Qt.createComponent(Qt.resolvedUrl("private/MobileDialogLayer.qml"));
814 }
815 return __mobileDialogLayerComponent;
816 }
817
818 function verifyPages(pages, properties): bool {
819 function validPage(page) {
820 //don't try adding an already existing page
821 if (page instanceof QT.Page && columnView.containsItem(page)) {
822 console.log(`Page ${page} is already in the PageRow`)
823 return false
824 }
825 return page instanceof QT.Page || page instanceof Component || typeof page === 'string'
826 || (typeof page === 'object' && typeof page.toString() === 'string')
827 }
828
829 // check page/pages that it is/they are valid
830 const pagesIsArr = Array.isArray(pages) && pages.length > 0
831 let isValidArrOfPages = pagesIsArr;
832
833 if (pagesIsArr) {
834 for (const page of pages) {
835 if (!validPage(page)) {
836 isValidArrOfPages = false;
837 break;
838 }
839 }
840 }
841
842 // check properties obejct/array object validity
843 const isProp = typeof properties === 'object';
844 const propsIsArr = Array.isArray(properties) && properties.length > 0
845 let isValidPropArr = propsIsArr;
846
847 if (propsIsArr) {
848 for (const prop of properties) {
849 if (typeof prop !== 'object') {
850 isValidPropArr = false;
851 break;
852 }
853 }
854 isValidPropArr = isValidPropArr && pages.length === properties.length
855 }
856
857 return (validPage(pages) || isValidArrOfPages)
858 && (!properties || (isProp || isValidPropArr))
859 }
860
861 function insertPage_unchecked(position, page, properties) {
862 columnView.pop(position - 1);
863
864 // figure out if more than one page is being pushed
865 let pages;
866 let propsArray = [];
867 if (page instanceof Array) {
868 pages = page;
869 page = pages.pop();
870 }
871 if (properties instanceof Array) {
872 propsArray = properties;
873 properties = propsArray.pop();
874 } else {
875 propsArray = [properties];
876 }
877
878 // push any extra defined pages onto the stack
879 if (pages) {
880 for (const i in pages) {
881 let tPage = pages[i];
882 let tProps = propsArray[i];
883
884 pagesLogic.initAndInsertPage(position, tPage, tProps);
885 ++position;
886 }
887 }
888
889 // initialize the page
890 const pageItem = pagesLogic.initAndInsertPage(position, page, properties);
891
892 pagePushed(pageItem);
893
894 return pageItem;
895 }
896
897 function getPageComponent(page): Component {
898 let pageComp;
899
900 if (page.createObject) {
901 // page defined as component
902 pageComp = page;
903 } else if (typeof page === "string") {
904 // page defined as string (a url)
905 pageComp = pagesLogic.componentCache[page];
906 if (!pageComp) {
907 pageComp = pagesLogic.componentCache[page] = Qt.createComponent(page);
908 }
909 } else if (typeof page === "object" && !(page instanceof Item) && page.toString !== undefined) {
910 // page defined as url (QML value type, not a string)
911 pageComp = pagesLogic.componentCache[page.toString()];
912 if (!pageComp) {
913 pageComp = pagesLogic.componentCache[page.toString()] = Qt.createComponent(page.toString());
914 }
915 }
916
917 return pageComp
918 }
919
920 function initPage(page, properties): QT.Page {
921 const pageComp = getPageComponent(page, properties);
922
923 if (pageComp) {
924 // instantiate page from component
925 // Important: The parent needs to be set otherwise a reference needs to be kept around
926 // to avoid the page being garbage collected.
927 page = pageComp.createObject(pagesLogic, properties || {});
928
929 if (pageComp.status === Component.Error) {
930 throw new Error("Error while loading page: " + pageComp.errorString());
931 }
932 } else {
933 // copy properties to the page
934 for (const prop in properties) {
935 if (page.hasOwnProperty(prop)) {
936 page[prop] = properties[prop];
937 }
938 }
939 }
940 return page;
941 }
942
943 function initAndInsertPage(position, page, properties): QT.Page {
944 page = initPage(page, properties);
945 columnView.insertItem(position, page);
946 return page;
947 }
948 }
949
950 Item {
951 RowLayout {
952 id: columnViewLayout
953 anchors {
954 fill: parent
955 // Use this instead Layout.topMargin in RowLayout seems to be unreliable
956 topMargin: -layersStack.y
957 }
958 spacing: 1
959 readonly property alias columnView: columnView
960 // set the pagestack of this and all children to root, otherwise
961 // they would automatically resolve to the layer's stackview
962 Kirigami.PageStack.pageStack: root
963
964 QQC2.Control {
965 id: sidebarControl
966 Layout.fillHeight: true
967 visible: contentItem !== null
968 leftPadding: root.leftSidebar ? root.leftSidebar.leftPadding : 0
969 topPadding: root.leftSidebar ? root.leftSidebar.topPadding : 0
970 rightPadding: root.leftSidebar ? root.leftSidebar.rightPadding : 0
971 bottomPadding: root.leftSidebar ? root.leftSidebar.bottomPadding : 0
972 }
973
974 Kirigami.ColumnView {
975 id: columnView
976 Layout.fillWidth: true
977 Layout.fillHeight: true
978
979 topPadding: globalToolBarUI.item && globalToolBarUI.item.breadcrumbVisible
980 ? globalToolBarUI.height : 0
981
982 // Internal hidden api for Page
983 readonly property Item __pageRow: root
984 acceptsMouse: Kirigami.Settings.isMobile
985 columnResizeMode: root.wideMode ? Kirigami.ColumnView.FixedColumns : Kirigami.ColumnView.SingleColumn
986 columnWidth: root.defaultColumnWidth
987 interactive: Qt.platform.os !== 'android'
988
989 onItemInserted: (position, item) => root.pageInserted(position, item);
990 onItemRemoved: item => root.pageRemoved(item);
991
992 onVisibleItemsChanged: {
993 // implementation of `popHiddenPages` option
994 if (root.popHiddenPages) {
995 // manually fetch lastItem here rather than use root.lastItem property, since that binding may not have updated yet
996 let lastItem = columnView.contentChildren[columnView.contentChildren.length - 1];
997 let trailingVisibleItem = columnView.trailingVisibleItem;
998
999 // pop every page that isn't visible and at the top of the stack
1000 while (lastItem && columnView.trailingVisibleItem &&
1001 lastItem !== columnView.trailingVisibleItem && columnView.containsItem(lastItem)) {
1002 root.pop();
1003 }
1004 }
1005 }
1006 }
1007 }
1008 }
1009}
PageRow implements a row-based navigation model, which can be used with a set of interlinked informat...
Definition PageRow.qml:21
OverlayDrawer leftSidebar
This property assigns a drawer as an internal left sidebar for this PageRow.
Definition PageRow.qml:170
Item lastItem
This property holds the last page in the row.
Definition PageRow.qml:35
int defaultColumnWidth
This property holds the default width for a column.
Definition PageRow.qml:112
bool wideMode
This property tells whether the PageRow is wide enough to show multiple pages.
Definition PageRow.qml:127
alias depth
This property holds the number of pages currently pushed onto the view.
Definition PageRow.qml:29
void push(page, properties)
This method pushes a page on the stack.
var initialPage
This property sets the initial page for this PageRow.
Definition PageRow.qml:55
alias visibleItems
This property holds all visible pages in the PageRow, excluding those which are scrolled away.
Definition PageRow.qml:86
bool popHiddenPages
This property holds whether to automatically pop pages at the top of the stack if they are not visibl...
Definition PageRow.qml:189
alias leadingVisibleItem
This property holds the first page in the PageRow that is at least partially visible.
Definition PageRow.qml:95
alias separatorVisible
This property sets whether the separators between pages should be displayed.
Definition PageRow.qml:136
alias currentItem
This property holds the currently visible/active page.
Definition PageRow.qml:44
alias globalToolBar
This property sets the appearance of an optional global toolbar for the whole PageRow.
Definition PageRow.qml:160
alias currentIndex
This property holds the index of the currently active page.
Definition PageRow.qml:50
alias interactive
This property sets whether it is possible to go back/forward by swiping with a gesture on the content...
Definition PageRow.qml:121
alias layers
This property holds the modal layers.
Definition PageRow.qml:180
alias columnView
This property holds the ColumnView that this PageRow owns.
Definition PageRow.qml:71
void pushDialogLayer(page, properties={}, windowProperties={})
Pushes a page as a new dialog on desktop and as a layer on mobile.
alias trailingVisibleItem
This property holds the last page in the PageRow that is at least partially visible.
Definition PageRow.qml:104
alias items
This property holds the present pages in the PageRow.
Definition PageRow.qml:78
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
KIOCORE_EXPORT TransferJob * get(const QUrl &url, LoadType reload=NoReload, JobFlags flags=DefaultFlags)
KGuiItem properties()
KGuiItem clear()
const QList< QKeySequence > & replace()
QTextStream & left(QTextStream &stream)
QTextStream & right(QTextStream &stream)
bool close()
void destroy(bool destroyWindow, bool destroySubWindows)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri May 2 2025 12:02:15 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.