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

0001 /*
0002  *  SPDX-FileCopyrightText: 2016-2023 Marco Martin <notmart@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: LGPL-2.0-or-later
0005  */
0006 
0007 import QtQuick
0008 import QtQuick.Layouts
0009 import QtQuick.Controls as QQC2
0010 import QtQuick.Templates as T
0011 import org.kde.kirigami as Kirigami
0012 
0013 /**
0014  * @brief An overlay sheet that covers the current Page content.
0015  *
0016  * Its contents can be scrolled up or down, scrolling all the way up or
0017  * all the way down, dismisses it.
0018  * Use this for big, modal dialogs or information display, that can't be
0019  * logically done as a new separate Page, even if potentially
0020  * are taller than the screen space.
0021  *
0022  * Example usage:
0023  * @code
0024  * Kirigami.OverlaySheet {
0025  *    ColumnLayout { ... }
0026  * }
0027  * Kirigami.OverlaySheet {
0028  *    ListView { ... }
0029  * }
0030  * @endcode
0031  *
0032  * It needs a single element declared inseide, do *not* override its contentItem
0033  *
0034  * @inherit QtQuick.Templates.Popup
0035  */
0036 T.Popup {
0037     id: root
0038 
0039     Kirigami.OverlayZStacking.layer: Kirigami.OverlayZStacking.FullScreen
0040     z: Kirigami.OverlayZStacking.z
0041 
0042     Kirigami.Theme.colorSet: Kirigami.Theme.View
0043     Kirigami.Theme.inherit: false
0044 
0045 //BEGIN Own Properties
0046 
0047     /**
0048      * @brief A title to be displayed in the header of this Sheet
0049      */
0050     property string title
0051 
0052     /**
0053      * @brief This property sets the visibility of the close button in the top-right corner.
0054      *
0055      * default: `Only shown in desktop mode`
0056      *
0057      */
0058     property bool showCloseButton: !Kirigami.Settings.isMobile
0059 
0060     /**
0061      * @brief This property holds an optional item which will be used as the sheet's header,
0062      * and will always be displayed.
0063      */
0064     property Item header: Kirigami.Heading {
0065         level: 2
0066         text: root.title
0067         elide: Text.ElideRight
0068 
0069         // use tooltip for long text that is elided
0070         T.ToolTip.visible: truncated && titleHoverHandler.hovered
0071         T.ToolTip.text: root.title
0072         HoverHandler {
0073             id: titleHoverHandler
0074         }
0075     }
0076 
0077     /**
0078      * @brief An optional item which will be used as the sheet's footer,
0079      * always kept on screen.
0080      */
0081     property Item footer
0082 
0083     default property alias flickableContentData: scrollView.contentData
0084 //END Own Properties
0085 
0086 //BEGIN Reimplemented Properties
0087     T.Overlay.modeless: Item {
0088         id: overlay
0089         Rectangle {
0090             x: sheetHandler.visualParent?.Kirigami.ScenePosition.x ?? 0
0091             y: sheetHandler.visualParent?.Kirigami.ScenePosition.y ?? 0
0092             width: sheetHandler.visualParent?.width ?? 0
0093             height: sheetHandler.visualParent?.height ?? 0
0094             color: Qt.rgba(0, 0, 0, 0.2)
0095         }
0096         Behavior on opacity {
0097             NumberAnimation {
0098                 property: "opacity"
0099                 easing.type: Easing.InOutQuad
0100                 duration: Kirigami.Units.longDuration
0101             }
0102         }
0103     }
0104 
0105     modal: false
0106     dim: true
0107 
0108     leftInset: -1
0109     rightInset: -1
0110     topInset: -1
0111     bottomInset: -1
0112 
0113     closePolicy: T.Popup.CloseOnEscape
0114     x: parent ? Math.round(parent.width / 2 - width / 2) : 0
0115     y: {
0116         if (!parent) {
0117             return 0;
0118         }
0119         const visualParentAdjust = sheetHandler.visualParent?.y ?? 0;
0120         const wantedPosition = Kirigami.Settings.isMobile
0121             ? parent.height - implicitHeight - Kirigami.Units.gridUnit
0122             : parent.height / 2 - implicitHeight / 2;
0123         return Math.round(Math.max(visualParentAdjust, wantedPosition, Kirigami.Units.gridUnit * 3));
0124     }
0125 
0126     implicitWidth: {
0127         let width = parent?.width ?? 0;
0128         if (!scrollView.itemForSizeHints) {
0129             return width;
0130         } else if (scrollView.itemForSizeHints.Layout.preferredWidth > 0) {
0131             return Math.min(width, scrollView.itemForSizeHints.Layout.preferredWidth);
0132         } else if (scrollView.itemForSizeHints.implicitWidth > 0) {
0133             return Math.min(width, scrollView.itemForSizeHints.implicitWidth);
0134         } else {
0135             return width;
0136         }
0137     }
0138     implicitHeight: {
0139         let h = parent?.height ?? 0;
0140         if (!scrollView.itemForSizeHints) {
0141             return h - y;
0142         } else if (scrollView.itemForSizeHints.Layout.preferredHeight > 0) {
0143             h = scrollView.itemForSizeHints.Layout.preferredHeight;
0144         } else if (scrollView.itemForSizeHints.implicitHeight > 0) {
0145             h = scrollView.itemForSizeHints.implicitHeight + Kirigami.Units.largeSpacing * 2;
0146         } else if (scrollView.itemForSizeHints instanceof Flickable && scrollView.itemForSizeHints.contentHeight > 0) {
0147             h = scrollView.itemForSizeHints.contentHeight + Kirigami.Units.largeSpacing * 2;
0148         } else {
0149             h = scrollView.itemForSizeHints.height;
0150         }
0151         h += headerItem.implicitHeight + footerParent.implicitHeight + topPadding + bottomPadding;
0152         return Math.min(h, parent.height - y)
0153     }
0154 //END Reimplemented Properties
0155 
0156 //BEGIN Signal handlers
0157     onVisibleChanged: {
0158         const flickable = scrollView.contentItem;
0159         flickable.contentY = flickable.originY - flickable.topMargin;
0160     }
0161     onHeaderChanged: headerItem.initHeader()
0162     onFooterChanged: {
0163         footer.parent = footerParent;
0164         footer.Layout.fillWidth = true;
0165     }
0166 
0167     Component.onCompleted: {
0168         Qt.callLater(() => {
0169             if (!root.parent && typeof applicationWindow !== "undefined") {
0170                 root.parent = applicationWindow().overlay
0171             }
0172             headerItem.initHeader();
0173         });
0174     }
0175 
0176     Connections {
0177         target: parent
0178         function onVisibleChanged() {
0179             if (!parent.visible) {
0180                 root.close();
0181             }
0182         }
0183     }
0184 //END Signal handlers
0185 
0186 //BEGIN UI
0187     contentItem: MouseArea {
0188         implicitWidth: mainLayout.implicitWidth
0189         implicitHeight: mainLayout.implicitHeight
0190         Kirigami.Theme.colorSet: root.Kirigami.Theme.colorSet
0191         Kirigami.Theme.inherit: false
0192 
0193         property real scenePressY
0194         property real lastY
0195         property bool dragStarted
0196         drag.filterChildren: true
0197         DragHandler {
0198             id: mouseDragBlocker
0199             target: null
0200             dragThreshold: 0
0201             acceptedDevices: PointerDevice.Mouse
0202             onActiveChanged: {
0203                 if (active) {
0204                     parent.dragStarted = false;
0205                 }
0206             }
0207         }
0208 
0209         onPressed: mouse => {
0210             scenePressY = mapToItem(null, mouse.x, mouse.y).y;
0211             lastY = scenePressY;
0212             dragStarted = false;
0213         }
0214         onPositionChanged: mouse => {
0215             if (mouseDragBlocker.active) {
0216                 return;
0217             }
0218             const currentY = mapToItem(null, mouse.x, mouse.y).y;
0219 
0220             if (dragStarted && currentY !== lastY) {
0221                 translation.y += currentY - lastY;
0222             }
0223             if (Math.abs(currentY - scenePressY) > Qt.styleHints.startDragDistance) {
0224                 dragStarted = true;
0225             }
0226             lastY = currentY;
0227         }
0228         onCanceled: restoreAnim.restart();
0229         onReleased: mouse => {
0230             if (mouseDragBlocker.active) {
0231                 return;
0232             }
0233             if (Math.abs(mapToItem(null, mouse.x, mouse.y).y - scenePressY) > Kirigami.Units.gridUnit * 5) {
0234                 root.close();
0235             } else {
0236                 restoreAnim.restart();
0237             }
0238         }
0239 
0240         ColumnLayout {
0241             id: mainLayout
0242             anchors.fill: parent
0243             spacing: 0
0244 
0245             // Even though we're not actually using any shadows here,
0246             // we're using a ShadowedRectangle instead of a regular
0247             // rectangle because it allows fine-grained control over which
0248             // corners to round, which we need here
0249             Kirigami.ShadowedRectangle {
0250                 id: headerItem
0251                 Layout.fillWidth: true
0252                 Layout.alignment: Qt.AlignTop
0253                 //Layout.margins: 1
0254                 visible: root.header || root.showCloseButton
0255                 implicitHeight: Math.max(headerParent.implicitHeight, closeIcon.height) + Kirigami.Units.smallSpacing * 2
0256                 z: 2
0257                 corners.topLeftRadius: Kirigami.Units.smallSpacing
0258                 corners.topRightRadius: Kirigami.Units.smallSpacing
0259                 Kirigami.Theme.colorSet: Kirigami.Theme.Header
0260                 Kirigami.Theme.inherit: false
0261                 color: Kirigami.Theme.backgroundColor
0262 
0263                 function initHeader() {
0264                     const h = root.header;
0265                     if (h) {
0266                         h.parent = headerParent;
0267                         h.anchors.fill = headerParent;
0268                     }
0269                 }
0270 
0271                 Item {
0272                     id: headerParent
0273                     implicitHeight: root.header?.implicitHeight ?? 0
0274                     anchors {
0275                         fill: parent
0276                         margins: Kirigami.Units.smallSpacing
0277                         leftMargin: Kirigami.Units.largeSpacing
0278                         rightMargin: (root.showCloseButton ? closeIcon.width : 0) + Kirigami.Units.smallSpacing
0279                     }
0280                 }
0281                 Kirigami.Icon {
0282                     id: closeIcon
0283 
0284                     readonly property bool tallHeader: headerItem.height > (Kirigami.Units.iconSizes.smallMedium + Kirigami.Units.largeSpacing + Kirigami.Units.largeSpacing)
0285 
0286                     anchors {
0287                         right: parent.right
0288                         rightMargin: Kirigami.Units.largeSpacing
0289                         verticalCenter: headerItem.verticalCenter
0290                         margins: Kirigami.Units.smallSpacing
0291                     }
0292 
0293                     // Apply the changes to the anchors imperatively, to first disable an anchor point
0294                     // before setting the new one, so the icon don't grow unexpectedly
0295                     onTallHeaderChanged: {
0296                         if (tallHeader) {
0297                             // We want to position the close button in the top-right corner if the header is very tall
0298                             anchors.verticalCenter = undefined
0299                             anchors.topMargin = Kirigami.Units.largeSpacing
0300                             anchors.top = headerItem.top
0301                         } else {
0302                             // but we want to vertically center it in a short header
0303                             anchors.top = undefined
0304                             anchors.topMargin = undefined
0305                             anchors.verticalCenter = headerItem.verticalCenter
0306                         }
0307                     }
0308                     Component.onCompleted: tallHeaderChanged()
0309 
0310                     z: 3
0311                     visible: root.showCloseButton
0312                     width: Kirigami.Units.iconSizes.smallMedium
0313                     height: width
0314                     source: closeMouseArea.containsMouse ? "window-close" : "window-close-symbolic"
0315                     active: closeMouseArea.containsMouse
0316                     MouseArea {
0317                         id: closeMouseArea
0318                         hoverEnabled: true
0319                         anchors.fill: parent
0320                         onClicked: root.close();
0321                     }
0322                 }
0323                 Kirigami.Separator {
0324                     anchors {
0325                         right: parent.right
0326                         left: parent.left
0327                         top: parent.bottom
0328                     }
0329                 }
0330             }
0331 
0332             // Here goes the main Sheet content
0333             QQC2.ScrollView {
0334                 id: scrollView
0335                 Layout.fillWidth: true
0336                 Layout.fillHeight: true
0337                 clip: true
0338                 T.ScrollBar.horizontal.policy: T.ScrollBar.AlwaysOff
0339 
0340                 property bool initialized: false
0341                 property Item itemForSizeHints
0342 
0343                 // Important to not even access contentItem before it has been spontaneously created
0344                 contentWidth: initialized ? contentItem.width : width
0345                 contentHeight: itemForSizeHints?.implicitHeight ?? 0
0346 
0347                 onContentItemChanged: {
0348                     initialized = true;
0349                     const flickable = contentItem as Flickable;
0350                     flickable.boundsBehavior = Flickable.StopAtBounds;
0351                     if ((flickable instanceof ListView) || (flickable instanceof GridView)) {
0352                         itemForSizeHints = flickable;
0353                         return;
0354                     }
0355                     const content = flickable.contentItem;
0356                     content.childrenChanged.connect(() => {
0357                         for (const item of content.children) {
0358                             item.anchors.margins = Kirigami.Units.largeSpacing;
0359                             item.anchors.top = content.top;
0360                             item.anchors.left = content.left;
0361                             item.anchors.right = content.right;
0362                         }
0363                         itemForSizeHints = content.children?.[0] ?? null;
0364                     });
0365                 }
0366             }
0367 
0368             // Optional footer
0369             Kirigami.Separator {
0370                 Layout.fillWidth: true
0371                 visible: footerParent.visible
0372             }
0373             RowLayout {
0374                 id: footerParent
0375                 Layout.fillWidth: true
0376                 Layout.leftMargin: Kirigami.Units.smallSpacing
0377                 Layout.topMargin: Kirigami.Units.smallSpacing
0378                 Layout.rightMargin: Kirigami.Units.smallSpacing
0379                 Layout.bottomMargin: Kirigami.Units.smallSpacing
0380                 visible: root.footer !== null
0381             }
0382         }
0383         Translate {
0384             id: translation
0385         }
0386         MouseArea {
0387             id: sheetHandler
0388             readonly property Item visualParent: root.parent?.contentItem ?? root.parent
0389             x: -root.x
0390             y: -root.y
0391             z: -1
0392             width:  visualParent?.width ?? 0
0393             height: (visualParent?.height ?? 0) * 2
0394 
0395             property var pressPos
0396             onPressed: mouse => {
0397                 pressPos = mapToItem(null, mouse.x, mouse.y)
0398             }
0399             onReleased: mouse => {
0400                 // onClicked is emitted even if the mouse was dragged a lot, so we have to check the Manhattan length by hand
0401                 // https://en.wikipedia.org/wiki/Taxicab_geometry
0402                 let pos = mapToItem(null, mouse.x, mouse.y)
0403                 if (Math.abs(pos.x - pressPos.x) + Math.abs(pos.y - pressPos.y) < Qt.styleHints.startDragDistance) {
0404                     root.close();
0405                 }
0406             }
0407 
0408             NumberAnimation {
0409                 id: restoreAnim
0410                 target: translation
0411                 property: "y"
0412                 from: translation.y
0413                 to: 0
0414                 easing.type: Easing.InOutQuad
0415                 duration: Kirigami.Units.longDuration
0416             }
0417             Component.onCompleted: {
0418                 root.contentItem.parent.transform = translation
0419                 root.contentItem.parent.clip = false
0420             }
0421         }
0422     }
0423 //END UI
0424 
0425 //BEGIN Transitions
0426     enter: Transition {
0427         ParallelAnimation {
0428             NumberAnimation {
0429                 property: "opacity"
0430                 from: 0
0431                 to: 1
0432                 easing.type: Easing.InOutQuad
0433                 duration: Kirigami.Units.longDuration
0434             }
0435             NumberAnimation {
0436                 target: translation
0437                 property: "y"
0438                 from: Kirigami.Units.gridUnit * 5
0439                 to: 0
0440                 easing.type: Easing.InOutQuad
0441                 duration: Kirigami.Units.longDuration
0442             }
0443         }
0444     }
0445 
0446     exit: Transition {
0447         ParallelAnimation {
0448             NumberAnimation {
0449                 property: "opacity"
0450                 from: 1
0451                 to: 0
0452                 easing.type: Easing.InOutQuad
0453                 duration: Kirigami.Units.longDuration
0454             }
0455             NumberAnimation {
0456                 target: translation
0457                 property: "y"
0458                 from: translation.y
0459                 to: translation.y >= 0 ? translation.y + Kirigami.Units.gridUnit * 5 : translation.y - Kirigami.Units.gridUnit * 5
0460                 easing.type: Easing.InOutQuad
0461                 duration: Kirigami.Units.longDuration
0462             }
0463         }
0464     }
0465 //END Transitions
0466 }
0467