Warning, /frameworks/kirigami/src/controls/SwipeListItem.qml is written in an unsupported language. File is not indexed.
0001 /* 0002 * SPDX-FileCopyrightText: 2019 Marco Martin <notmart@gmail.com> 0003 * 0004 * SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 import QtQuick 0008 import QtQuick.Controls as QQC2 0009 import QtQuick.Layouts 0010 import QtQuick.Templates as T 0011 import org.kde.kirigami as Kirigami 0012 import "private" 0013 0014 /** 0015 * An item delegate intended to support extra actions obtainable 0016 * by uncovering them by dragging away the item with the handle. 0017 * 0018 * This acts as a container for normal list items. 0019 * 0020 * Example usage: 0021 * @code 0022 * ListView { 0023 * model: myModel 0024 * delegate: SwipeListItem { 0025 * QQC2.Label { 0026 * text: model.text 0027 * } 0028 * actions: [ 0029 * Action { 0030 * icon.name: "document-decrypt" 0031 * onTriggered: print("Action 1 clicked") 0032 * }, 0033 * Action { 0034 * icon.name: model.action2Icon 0035 * onTriggered: //do something 0036 * } 0037 * ] 0038 * } 0039 * 0040 * } 0041 * @endcode 0042 * 0043 * @inherit QtQuick.Templates.SwipeDelegate 0044 */ 0045 QQC2.SwipeDelegate { 0046 id: listItem 0047 0048 //BEGIN properties 0049 /** 0050 * @brief This property sets whether the item should emit signals related to mouse interaction. 0051 * 0052 * default: ``true`` 0053 * 0054 * @deprecated Use hoverEnabled instead. 0055 * @property bool supportsMouseEvents 0056 */ 0057 property alias supportsMouseEvents: listItem.hoverEnabled 0058 0059 /** 0060 * @brief This property tells whether the cursor is currently hovering over the item. 0061 * 0062 * On mobile touch devices, this will be true only when pressed. 0063 * 0064 * @see QtQuick.Templates.ItemDelegate::hovered 0065 * @deprecated This will be removed in KF6; use the ``hovered`` property instead. 0066 * @property bool containsMouse 0067 */ 0068 readonly property alias containsMouse: listItem.hovered 0069 0070 /** 0071 * @brief This property sets whether instances of this list item will alternate 0072 * between two colors, helping readability. 0073 * 0074 * It is suggested to use this only when implementing a view with multiple columns. 0075 * 0076 * default: ``false`` 0077 * 0078 * @since 2.7 0079 */ 0080 property bool alternatingBackground: false 0081 0082 /** 0083 * @brief This property sets whether this item is a section delegate. 0084 * 0085 * Setting this to true will make the list item look like a "title" for items under it. 0086 * 0087 * default: ``false`` 0088 * 0089 * @see ListSectionHeader 0090 */ 0091 property bool sectionDelegate: false 0092 0093 /** 0094 * @brief This property sets whether the separator is visible. 0095 * 0096 * The separator is a line between this and the item under it. 0097 * 0098 * default: ``false`` 0099 */ 0100 property bool separatorVisible: false 0101 0102 /** 0103 * @brief This property holds the background color of the list item. 0104 * 0105 * It is advised to use the default value. 0106 * default: ``Kirigami.Theme.backgroundColor`` 0107 */ 0108 property color backgroundColor: Kirigami.Theme.backgroundColor 0109 0110 /** 0111 * @brief This property holds the background color to be used when 0112 * background alternating is enabled. 0113 * 0114 * It is advised to use the default value. 0115 * default: ``Kirigami.Theme.alternateBackgroundColor`` 0116 * 0117 * @since 2.7 0118 */ 0119 property color alternateBackgroundColor: Kirigami.Theme.alternateBackgroundColor 0120 0121 /** 0122 * @brief This property holds the color of the background 0123 * when the item is pressed or selected. 0124 * 0125 * It is advised to use the default value. 0126 * default: ``Kirigami.Theme.highlightColor`` 0127 */ 0128 property color activeBackgroundColor: Kirigami.Theme.highlightColor 0129 0130 /** 0131 * @brief This property holds the color of the text in the item. 0132 * 0133 * It is advised to use the default value. 0134 * default: ``Theme.textColor`` 0135 * 0136 * If custom text elements are inserted in a SwipeListItem, 0137 * their color will have to be manually set with this property. 0138 */ 0139 property color textColor: Kirigami.Theme.textColor 0140 0141 /** 0142 * @brief This property holds the color of the text when the item is pressed or selected. 0143 * 0144 * It is advised to use the default value. 0145 * default: ``Kirigami.Theme.highlightedTextColor`` 0146 * 0147 * If custom text elements are inserted in a SwipeListItem, 0148 * their color property will have to be manually bound with this property 0149 */ 0150 property color activeTextColor: Kirigami.Theme.highlightedTextColor 0151 0152 /** 0153 * @brief This property tells whether actions are visible and interactive. 0154 * 0155 * True if it's possible to see and interact with the item's actions. 0156 * 0157 * Actions become hidden while editing of an item, for example. 0158 * 0159 * @since 2.5 0160 */ 0161 readonly property bool actionsVisible: actionsLayout.hasVisibleActions 0162 0163 /** 0164 * @brief This property sets whether actions behind this SwipeListItem will always be visible. 0165 * 0166 * default: `true in desktop and tablet mode` 0167 * 0168 * @since 2.15 0169 */ 0170 property bool alwaysVisibleActions: !Kirigami.Settings.isMobile 0171 0172 /** 0173 * @brief This property holds actions of the list item. 0174 * 0175 * At most 4 actions can be revealed when sliding away the list item; 0176 * others will be shown in the overflow menu. 0177 */ 0178 property list<T.Action> actions 0179 0180 /** 0181 * @brief This property holds the width of the overlay. 0182 * 0183 * The value can represent the width of the handle component or the action layout. 0184 * 0185 * @since 2.19 0186 * @property real overlayWidth 0187 */ 0188 readonly property alias overlayWidth: overlayLoader.width 0189 0190 //END properties 0191 0192 LayoutMirroring.childrenInherit: true 0193 0194 hoverEnabled: true 0195 implicitWidth: contentItem ? implicitContentWidth : Kirigami.Units.gridUnit * 12 0196 width: parent ? parent.width : implicitWidth 0197 implicitHeight: Math.max(Kirigami.Units.gridUnit * 2, implicitContentHeight) + topPadding + bottomPadding 0198 0199 padding: !listItem.alwaysVisibleActions && Kirigami.Settings.tabletMode ? Kirigami.Units.largeSpacing : Kirigami.Units.smallSpacing 0200 0201 leftPadding: padding * 2 + (mirrored ? overlayLoader.paddingOffset : 0) 0202 rightPadding: padding * 2 + (mirrored ? 0 : overlayLoader.paddingOffset) 0203 0204 topPadding: padding 0205 bottomPadding: padding 0206 0207 contentItem: Item {} 0208 QtObject { 0209 id: internal 0210 0211 property Flickable view: listItem.ListView.view || (listItem.parent ? (listItem.parent.ListView.view || (listItem.parent instanceof Flickable ? listItem.parent : null)) : null) 0212 0213 function viewHasPropertySwipeFilter(): bool { 0214 return view && view.parent && view.parent.parent && "_swipeFilter" in view.parent.parent; 0215 } 0216 0217 readonly property QtObject swipeFilterItem: (viewHasPropertySwipeFilter() && view.parent.parent._swipeFilter) ? view.parent.parent._swipeFilter : null 0218 0219 readonly property bool edgeEnabled: swipeFilterItem ? swipeFilterItem.currentItem === listItem || swipeFilterItem.currentItem === listItem.parent : false 0220 0221 // install the SwipeItemEventFilter 0222 onViewChanged: { 0223 if (listItem.alwaysVisibleActions || !Kirigami.Settings.tabletMode) { 0224 return; 0225 } 0226 if (viewHasPropertySwipeFilter() && Kirigami.Settings.tabletMode && !internal.view.parent.parent._swipeFilter) { 0227 const component = Qt.createComponent(Qt.resolvedUrl("../private/SwipeItemEventFilter.qml")); 0228 internal.view.parent.parent._swipeFilter = component.createObject(internal.view.parent.parent); 0229 component.destroy(); 0230 } 0231 } 0232 } 0233 0234 Connections { 0235 target: Kirigami.Settings 0236 function onTabletModeChanged() { 0237 if (!internal.viewHasPropertySwipeFilter()) { 0238 return; 0239 } 0240 if (Kirigami.Settings.tabletMode) { 0241 if (!internal.swipeFilterItem) { 0242 const component = Qt.createComponent(Qt.resolvedUrl("../private/SwipeItemEventFilter.qml")); 0243 listItem.ListView.view.parent.parent._swipeFilter = component.createObject(listItem.ListView.view.parent.parent); 0244 component.destroy(); 0245 } 0246 } else { 0247 if (listItem.ListView.view.parent.parent._swipeFilter) { 0248 listItem.ListView.view.parent.parent._swipeFilter.destroy(); 0249 slideAnim.to = 0; 0250 slideAnim.restart(); 0251 } 0252 } 0253 } 0254 } 0255 0256 //BEGIN items 0257 Loader { 0258 id: overlayLoader 0259 readonly property int paddingOffset: (visible ? width : 0) + Kirigami.Units.smallSpacing 0260 readonly property var theAlias: anchors 0261 function validate(want, defaultValue) { 0262 const expectedLeftPadding = () => listItem.padding * 2 + (listItem.mirrored ? overlayLoader.paddingOffset : 0) 0263 const expectedRightPadding = () => listItem.padding * 2 + (listItem.mirrored ? 0 : overlayLoader.paddingOffset) 0264 0265 const warningText = 0266 `Don't override the leftPadding or rightPadding on a SwipeListItem!\n` + 0267 `This makes it impossible for me to adjust my layout as I need to for various usecases.\n` + 0268 `I'll try to fix the mistake for you, but you should remove your overrides from your app's code entirely.\n` + 0269 `If I can't fix the paddings, I'll fall back to a default layout, but it'll be slightly incorrect and lacks\n` + 0270 `adaptations needed for touch screens and right-to-left languages, among other things.` 0271 0272 if (listItem.leftPadding != expectedLeftPadding() || listItem.rightPadding != expectedRightPadding()) { 0273 listItem.leftPadding = Qt.binding(expectedLeftPadding) 0274 listItem.rightPadding = Qt.binding(expectedRightPadding) 0275 console.warn(warningText) 0276 return defaultValue 0277 } 0278 0279 return want 0280 } 0281 anchors { 0282 right: validate(listItem.mirrored ? undefined : (contentItem ? contentItem.right : undefined), contentItem ? contentItem.right : undefined) 0283 rightMargin: validate(-paddingOffset, 0) 0284 left: validate(!listItem.mirrored ? undefined : (contentItem ? contentItem.left : undefined), undefined) 0285 leftMargin: validate(-paddingOffset, 0) 0286 top: parent.top 0287 bottom: parent.bottom 0288 } 0289 LayoutMirroring.enabled: false 0290 0291 parent: listItem 0292 z: contentItem ? contentItem.z + 1 : 0 0293 width: item ? item.implicitWidth : actionsLayout.implicitWidth 0294 active: !listItem.alwaysVisibleActions && Kirigami.Settings.tabletMode 0295 visible: listItem.actionsVisible && opacity > 0 0296 asynchronous: true 0297 sourceComponent: handleComponent 0298 opacity: listItem.alwaysVisibleActions || Kirigami.Settings.tabletMode || listItem.hovered ? 1 : 0 0299 Behavior on opacity { 0300 OpacityAnimator { 0301 id: opacityAnim 0302 duration: Kirigami.Units.veryShortDuration 0303 easing.type: Easing.InOutQuad 0304 } 0305 } 0306 } 0307 0308 Component { 0309 id: handleComponent 0310 0311 MouseArea { 0312 id: dragButton 0313 anchors { 0314 right: parent.right 0315 } 0316 implicitWidth: Kirigami.Units.iconSizes.smallMedium 0317 0318 preventStealing: true 0319 readonly property real openPosition: (listItem.width - width - listItem.leftPadding * 2)/listItem.width 0320 property real startX: 0 0321 property real lastPosition: 0 0322 property bool openIntention 0323 0324 onPressed: mouse => { 0325 startX = mapToItem(listItem, 0, 0).x; 0326 } 0327 onClicked: mouse => { 0328 if (Math.abs(mapToItem(listItem, 0, 0).x - startX) > Qt.styleHints.startDragDistance) { 0329 return; 0330 } 0331 if (listItem.mirrored) { 0332 if (listItem.swipe.position < 0.5) { 0333 slideAnim.to = openPosition 0334 } else { 0335 slideAnim.to = 0 0336 } 0337 } else { 0338 if (listItem.swipe.position > -0.5) { 0339 slideAnim.to = -openPosition 0340 } else { 0341 slideAnim.to = 0 0342 } 0343 } 0344 slideAnim.restart(); 0345 } 0346 onPositionChanged: mouse => { 0347 const pos = mapToItem(listItem, mouse.x, mouse.y); 0348 0349 if (listItem.mirrored) { 0350 listItem.swipe.position = Math.max(0, Math.min(openPosition, (pos.x / listItem.width))); 0351 openIntention = listItem.swipe.position > lastPosition; 0352 } else { 0353 listItem.swipe.position = Math.min(0, Math.max(-openPosition, (pos.x / (listItem.width -listItem.rightPadding) - 1))); 0354 openIntention = listItem.swipe.position < lastPosition; 0355 } 0356 lastPosition = listItem.swipe.position; 0357 } 0358 onReleased: mouse => { 0359 if (listItem.mirrored) { 0360 if (openIntention) { 0361 slideAnim.to = openPosition 0362 } else { 0363 slideAnim.to = 0 0364 } 0365 } else { 0366 if (openIntention) { 0367 slideAnim.to = -openPosition 0368 } else { 0369 slideAnim.to = 0 0370 } 0371 } 0372 slideAnim.restart(); 0373 } 0374 0375 Kirigami.Icon { 0376 id: handleIcon 0377 anchors.fill: parent 0378 selected: listItem.checked || (listItem.down && !listItem.checked && !listItem.sectionDelegate) 0379 source: (listItem.mirrored ? (listItem.background.x < listItem.background.width/2 ? "overflow-menu-right" : "overflow-menu-left") : (listItem.background.x < -listItem.background.width/2 ? "overflow-menu-right" : "overflow-menu-left")) 0380 } 0381 0382 Connections { 0383 id: swipeFilterConnection 0384 0385 target: internal.edgeEnabled ? internal.swipeFilterItem : null 0386 function onPeekChanged() { 0387 if (!listItem.actionsVisible) { 0388 return; 0389 } 0390 0391 if (listItem.mirrored) { 0392 listItem.swipe.position = Math.max(0, Math.min(dragButton.openPosition, internal.swipeFilterItem.peek)); 0393 dragButton.openIntention = listItem.swipe.position > dragButton.lastPosition; 0394 0395 } else { 0396 listItem.swipe.position = Math.min(0, Math.max(-dragButton.openPosition, -internal.swipeFilterItem.peek)); 0397 dragButton.openIntention = listItem.swipe.position < dragButton.lastPosition; 0398 } 0399 0400 dragButton.lastPosition = listItem.swipe.position; 0401 } 0402 function onPressed(mouse) { 0403 if (internal.edgeEnabled) { 0404 dragButton.pressed(mouse); 0405 } 0406 } 0407 function onClicked(mouse) { 0408 if (Math.abs(listItem.background.x) < Kirigami.Units.gridUnit && internal.edgeEnabled) { 0409 dragButton.clicked(mouse); 0410 } 0411 } 0412 function onReleased(mouse) { 0413 if (internal.edgeEnabled) { 0414 dragButton.released(mouse); 0415 } 0416 } 0417 function onCurrentItemChanged() { 0418 if (!internal.edgeEnabled) { 0419 slideAnim.to = 0; 0420 slideAnim.restart(); 0421 } 0422 } 0423 } 0424 } 0425 } 0426 0427 // TODO: expose in API? 0428 Component { 0429 id: actionsBackgroundDelegate 0430 MouseArea { 0431 0432 anchors.fill: parent 0433 0434 // QQC2.SwipeDelegate.onPressedChanged is broken with touch 0435 onClicked: mouse => { 0436 slideAnim.to = 0; 0437 slideAnim.restart(); 0438 } 0439 Rectangle { 0440 anchors.fill: parent 0441 color: parent.pressed ? Qt.darker(Kirigami.Theme.backgroundColor, 1.1) : Qt.darker(Kirigami.Theme.backgroundColor, 1.05) 0442 } 0443 0444 visible: listItem.swipe.position != 0 0445 0446 0447 EdgeShadow { 0448 edge: Qt.TopEdge 0449 visible: background.x != 0 0450 anchors { 0451 right: parent.right 0452 left: parent.left 0453 top: parent.top 0454 } 0455 } 0456 EdgeShadow { 0457 edge: listItem.mirrored ? Qt.RightEdge : Qt.LeftEdge 0458 x: listItem.mirrored ? listItem.background.x - width : (listItem.background.x + listItem.background.width) 0459 visible: background.x != 0 0460 anchors { 0461 top: parent.top 0462 bottom: parent.bottom 0463 } 0464 } 0465 } 0466 } 0467 0468 0469 RowLayout { 0470 id: actionsLayout 0471 0472 LayoutMirroring.enabled: listItem.mirrored 0473 anchors { 0474 right: parent.right 0475 top: parent.top 0476 bottom: parent.bottom 0477 rightMargin: Kirigami.Units.smallSpacing 0478 } 0479 visible: parent !== listItem 0480 parent: !listItem.alwaysVisibleActions && Kirigami.Settings.tabletMode 0481 ? listItem.swipe.leftItem || listItem.swipe.rightItem || listItem 0482 : overlayLoader 0483 0484 property bool hasVisibleActions: false 0485 0486 function updateVisibleActions(definitelyVisible: bool) { 0487 hasVisibleActions = definitelyVisible || listItem.actions.some(isActionVisible); 0488 } 0489 0490 function isActionVisible(action: T.Action): bool { 0491 return (action instanceof Kirigami.Action) ? action.visible : true; 0492 } 0493 0494 Repeater { 0495 model: listItem.actions 0496 0497 delegate: QQC2.ToolButton { 0498 required property T.Action modelData 0499 0500 action: modelData 0501 display: T.AbstractButton.IconOnly 0502 visible: actionsLayout.isActionVisible(action) 0503 0504 onVisibleChanged: actionsLayout.updateVisibleActions(visible); 0505 Component.onCompleted: actionsLayout.updateVisibleActions(visible); 0506 Component.onDestruction: actionsLayout.updateVisibleActions(visible); 0507 0508 QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay 0509 QQC2.ToolTip.visible: (Kirigami.Settings.tabletMode ? pressed : hovered) && QQC2.ToolTip.text.length > 0 0510 QQC2.ToolTip.text: (action as Kirigami.Action)?.tooltip ?? action?.text ?? "" 0511 0512 onClicked: { 0513 slideAnim.to = 0; 0514 slideAnim.restart(); 0515 } 0516 0517 Accessible.name: text 0518 Accessible.description: (action as Kirigami.Action)?.tooltip ?? "" 0519 } 0520 } 0521 } 0522 0523 swipe { 0524 enabled: false 0525 right: listItem.alwaysVisibleActions || listItem.mirrored || !Kirigami.Settings.tabletMode ? null : actionsBackgroundDelegate 0526 left: listItem.alwaysVisibleActions || listItem.mirrored && Kirigami.Settings.tabletMode ? actionsBackgroundDelegate : null 0527 } 0528 NumberAnimation { 0529 id: slideAnim 0530 duration: Kirigami.Units.longDuration 0531 easing.type: Easing.InOutQuad 0532 target: listItem.swipe 0533 property: "position" 0534 from: listItem.swipe.position 0535 } 0536 //END items 0537 }