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 }