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 }