Warning, /libraries/kirigami-addons/src/components/SearchPopupField.qml is written in an unsupported language. File is not indexed.
0001 // SPDX-FileCopyrightText: 2021 Jonah BrĂ¼chert <jbb@kaidan.im> 0002 // SPDX-FileCopyrightText: 2023 Mathis BrĂ¼chert <mbb@kaidan.im> 0003 // SPDX-FileCopyrightText: 2023 Carl Schwan <carl@carlschwan.eu> 0004 // SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk> 0005 // 0006 // SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0007 0008 import QtQuick 2.15 0009 import QtQuick.Controls 2.15 as QQC2 0010 import QtQuick.Templates 2.15 as T 0011 import QtQuick.Layouts 1.15 0012 import Qt.labs.qmlmodels 1.0 0013 import org.kde.kirigami 2.20 as Kirigami 0014 0015 /** 0016 * SearchField with a Popup to show autocompletion entries or search results 0017 * 0018 * @since KirigamiAddons.labs.components 1.0 0019 */ 0020 QQC2.Control { 0021 id: root 0022 0023 /** 0024 * This property holds the content item of the popup. 0025 * 0026 * Overflow will be automatically be handled as popupContentItem is 0027 * contained inside a ScrollView. 0028 * 0029 * This is the default element of SearchPopupField. 0030 * 0031 * ```qml 0032 * SearchPopupField { 0033 * ListView { 0034 * model: SearchModel {} 0035 * delegate: QQC2.ItemDelegate {} 0036 * 0037 * Kirigami.PlaceholderMessage { 0038 * id: loadingPlaceholder 0039 * anchors.centerIn: parent 0040 * width: parent.width - Kirigami.Units.gridUnit * 4 0041 * // ... 0042 * } 0043 * } 0044 * } 0045 * ``` 0046 * 0047 * @since KirigamiAddons.labs.components 1.0 0048 */ 0049 default property alias popupContentItem: scrollView.contentItem 0050 0051 /** 0052 * This property holds the text of the search field. 0053 * 0054 * @since KirigamiAddons.labs.components 1.0 0055 */ 0056 property alias text: root.searchField.text 0057 0058 /** 0059 * @brief This property sets whether to delay automatic acceptance of the search input. 0060 * 0061 * Set this to true if your search is expensive (such as for online 0062 * operations or in exceptionally slow data sets) and want to delay it 0063 * for 2.5 seconds. 0064 * 0065 * @note If you must have immediate feedback (filter-style), use the 0066 * text property directly instead of accepted() 0067 * 0068 * default: ``false`` 0069 * 0070 * @since KirigamiAddons.labs.components 1.0 0071 */ 0072 property alias delaySearch: root.searchField.delaySearch 0073 0074 /** 0075 * @brief This property sets whether the accepted signal is fired automatically 0076 * when the text is changed. 0077 * 0078 * Setting this to false will require that the user presses return or enter 0079 * (the same way a QtQuick.Controls.TextInput works). 0080 * 0081 * default: ``false`` 0082 * 0083 * @since KirigamiAddons.labs.components 1.0 0084 */ 0085 property alias autoAccept: root.searchField.autoAccept 0086 0087 /** 0088 * This property holds whether there is space available on the left. 0089 * 0090 * This is used by the left shadow. 0091 * 0092 * @since KirigamiAddons.labs.components 1.0 0093 * @deprecated Was not really used by anything. 0094 */ 0095 property bool spaceAvailableLeft: true 0096 0097 /** 0098 * This property holds whether there is space available on the left. 0099 * 0100 * This is used by the right shadow. 0101 * 0102 * @since KirigamiAddons.labs.components 1.0 0103 * @deprecated Was not really used by anything. 0104 */ 0105 property bool spaceAvailableRight: true 0106 0107 /** 0108 * @brief This hold the focus state of the internal SearchField. 0109 */ 0110 property alias fieldFocus: searchField.focus 0111 0112 /** 0113 * This signal is triggered when the user trigger a search. 0114 */ 0115 signal accepted() 0116 0117 property alias popup: popup 0118 0119 property Kirigami.SearchField searchField: Kirigami.SearchField { 0120 id: searchField 0121 anchors.left: parent ? parent.left : undefined 0122 anchors.right: parent ? parent.right : undefined 0123 selectByMouse: true 0124 0125 KeyNavigation.tab: scrollView.contentItem 0126 KeyNavigation.down: scrollView.contentItem 0127 0128 onActiveFocusChanged: if (activeFocus) { 0129 // Don't mess with popups and reparenting inside focus change handler. 0130 // Especially nested popups hate that: it may break all focus management 0131 // on a scene until restart. 0132 Qt.callLater(() => { 0133 // TODO: Kirigami.OverlayZStacking fails to find and bind to 0134 // parent logical popup when parent item is itself reparented 0135 // on the fly. 0136 if (typeof Kirigami.OverlayZStacking !== "undefined") { 0137 root.popup.z = Qt.binding(() => root.popup.Kirigami.OverlayZStacking.z); 0138 } 0139 root.popup.open(); 0140 }); 0141 } 0142 0143 onAccepted: { 0144 if (root.autoAccept) { 0145 if (text.length > 2) { 0146 root.accepted() 0147 } 0148 } else if (text.length === 0) { 0149 root.popup.close(); 0150 } else { 0151 root.accepted(); 0152 } 0153 } 0154 } 0155 0156 contentItem: Item { 0157 implicitHeight: searchField.implicitHeight 0158 implicitWidth: searchField.implicitWidth 0159 children: searchField 0160 } 0161 0162 padding: 0 0163 topPadding: undefined 0164 leftPadding: undefined 0165 rightPadding: undefined 0166 bottomPadding: undefined 0167 verticalPadding: undefined 0168 horizontalPadding: undefined 0169 0170 focusPolicy: Qt.NoFocus 0171 activeFocusOnTab: true 0172 0173 onActiveFocusChanged: { 0174 if (activeFocus) { 0175 searchField.forceActiveFocus(); 0176 } 0177 } 0178 0179 onVisibleChanged: { 0180 if (!visible) { 0181 popup.close(); 0182 } 0183 } 0184 0185 function __handoverChild(child: Item, oldParent: Item, newParent: Item) { 0186 // It used to be more complicated with QQC2.Control::contentItem 0187 // handover. But plain Items are very simple to deal with, and they 0188 // don't attempt to hide old contentItem by setting their visible=false. 0189 child.parent = newParent; 0190 } 0191 0192 T.Popup { 0193 id: popup 0194 0195 Component.onCompleted: { 0196 // TODO KF6: port to declarative bindings. 0197 if (typeof Kirigami.OverlayZStacking !== "undefined") { 0198 Kirigami.OverlayZStacking.layer = Kirigami.OverlayZStacking.Dialog; 0199 z = Qt.binding(() => Kirigami.OverlayZStacking.z); 0200 } 0201 } 0202 0203 readonly property real collapsedHeight: searchField.implicitHeight 0204 + topMargin + bottomMargin + topPadding + bottomPadding 0205 0206 // How much vertical space this popup is actually going to take, 0207 // considering that margins will push it inside and shrink if needed. 0208 readonly property real realisticHeight: { 0209 const wantedHeight = searchField.implicitHeight + Kirigami.Units.gridUnit * 20; 0210 const overlay = root.QQC2.Overlay.overlay; 0211 if (!overlay) { 0212 return 0; 0213 } 0214 return Math.min(wantedHeight, overlay.height - topMargin - bottomMargin); 0215 } 0216 0217 readonly property real realisticContentHeight: realisticHeight - topPadding - bottomPadding 0218 0219 // y offset from parent/root control if there's not enough space on 0220 // the bottom, so popup is being pushed upward. 0221 readonly property real yOffset: { 0222 const overlay = root.QQC2.Overlay.overlay; 0223 if (!overlay) { 0224 return 0; 0225 } 0226 return Math.max(-root.Kirigami.ScenePosition.y, Math.min(0, overlay.height - root.Kirigami.ScenePosition.y - realisticHeight)); 0227 } 0228 0229 clip: false 0230 parent: root 0231 0232 // make sure popup is being pushed in-bounds if it is too large or root control is (partially) out of bounds 0233 margins: 0 0234 0235 leftPadding: dialogRoundedBackground.border.width 0236 rightPadding: dialogRoundedBackground.border.width 0237 bottomPadding: dialogRoundedBackground.border.width 0238 x: -leftPadding 0239 y: 0 // initial value, will be managed by enter/exit transitions 0240 0241 implicitWidth: root.width + leftPadding + rightPadding 0242 height: popup.collapsedHeight // initial binding, will be managed by enter/exit transitions 0243 0244 onVisibleChanged: { 0245 searchField.QQC2.ToolTip.hide(); 0246 if (visible) { 0247 root.__handoverChild(searchField, root.contentItem, fieldContainer); 0248 searchField.forceActiveFocus(); 0249 } else { 0250 root.__handoverChild(searchField, fieldContainer, root.contentItem); 0251 } 0252 } 0253 0254 onAboutToHide: { 0255 searchField.focus = false; 0256 } 0257 0258 enter: Transition { 0259 SequentialAnimation { 0260 // cross-fade search field's background with popup's bigger rounded background 0261 ParallelAnimation { 0262 NumberAnimation { 0263 target: searchField.background 0264 property: "opacity" 0265 to: 0 0266 easing.type: Easing.OutCubic 0267 duration: Kirigami.Units.shortDuration 0268 } 0269 NumberAnimation { 0270 target: dialogRoundedBackground 0271 property: "opacity" 0272 to: 1 0273 easing.type: Easing.OutCubic 0274 duration: Kirigami.Units.shortDuration 0275 } 0276 } 0277 // push Y upward (if needed) and expand at the same time 0278 ParallelAnimation { 0279 NumberAnimation { 0280 property: "y" 0281 easing.type: Easing.OutCubic 0282 duration: Kirigami.Units.longDuration 0283 to: popup.yOffset 0284 } 0285 NumberAnimation { 0286 property: "height" 0287 easing.type: Easing.OutCubic 0288 duration: Kirigami.Units.longDuration 0289 to: popup.realisticHeight 0290 } 0291 } 0292 } 0293 } 0294 0295 // Rebind animated properties in case enter/exit transition was skipped. 0296 onOpened: { 0297 searchField.background.opacity = 0; 0298 dialogRoundedBackground.opacity = 1; 0299 // Make sure height stays sensible if window is resized while popup is open. 0300 popup.y = Qt.binding(() => popup.yOffset); 0301 popup.height = Qt.binding(() => popup.realisticHeight); 0302 } 0303 0304 exit: Transition { 0305 SequentialAnimation { 0306 // return Y back to root control's position (if needed) and collapse at the same time 0307 ParallelAnimation { 0308 NumberAnimation { 0309 property: "y" 0310 easing.type: Easing.OutCubic 0311 duration: Kirigami.Units.longDuration 0312 to: 0 0313 } 0314 NumberAnimation { 0315 property: "height" 0316 easing.type: Easing.OutCubic 0317 duration: Kirigami.Units.longDuration 0318 to: popup.collapsedHeight 0319 } 0320 } 0321 // cross-fade search field's background with popup's bigger rounded background 0322 ParallelAnimation { 0323 NumberAnimation { 0324 target: searchField.background 0325 property: "opacity" 0326 to: 1 0327 easing.type: Easing.OutCubic 0328 duration: Kirigami.Units.shortDuration 0329 } 0330 NumberAnimation { 0331 target: dialogRoundedBackground 0332 property: "opacity" 0333 to: 0 0334 easing.type: Easing.OutCubic 0335 duration: Kirigami.Units.shortDuration 0336 } 0337 } 0338 } 0339 } 0340 0341 // Rebind animated properties in case enter/exit transition was skipped. 0342 onClosed: { 0343 searchField.background.opacity = 1; 0344 dialogRoundedBackground.opacity = 0; 0345 // Make sure height stays sensible if search field is resized while popup is closed. 0346 popup.y = 0; 0347 popup.height = Qt.binding(() => popup.collapsedHeight); 0348 } 0349 0350 background: DialogRoundedBackground { 0351 id: dialogRoundedBackground 0352 0353 // initial value, will be managed by enter/exit transitions 0354 opacity: 0 0355 } 0356 0357 contentItem: Item { 0358 // clip with rounded corners 0359 layer.enabled: popup.enter.running || popup.exit.running 0360 layer.effect: Kirigami.ShadowedTexture { 0361 // color is not needed, we are here for the clipping only. 0362 color: "transparent" 0363 // border is not needed, as is is already accounted by 0364 // padding. But radius has to be adjusted for that padding. 0365 radius: dialogRoundedBackground.radius - popup.leftPadding - popup.bottomPadding 0366 } 0367 0368 ColumnLayout { 0369 anchors { 0370 top: parent.top 0371 left: parent.left 0372 right: parent.right 0373 } 0374 height: popup.realisticContentHeight 0375 spacing: 0 0376 0377 Item { 0378 id: fieldContainer 0379 implicitWidth: searchField.implicitWidth 0380 implicitHeight: searchField.implicitHeight 0381 Layout.fillWidth: true 0382 } 0383 0384 Kirigami.Separator { 0385 Layout.fillWidth: true 0386 } 0387 0388 QQC2.ScrollView { 0389 id: scrollView 0390 0391 Kirigami.Theme.colorSet: Kirigami.Theme.View 0392 Kirigami.Theme.inherit: false 0393 0394 Layout.fillHeight: true 0395 Layout.fillWidth: true 0396 } 0397 } 0398 } 0399 } 0400 }