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