diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -18,6 +18,7 @@ enums.cpp delegaterecycler.cpp icon.cpp + swipeaction.cpp settings.cpp formlayoutattached.cpp pagepool.cpp diff --git a/src/controls/ActionRow.qml b/src/controls/ActionRow.qml new file mode 100644 --- /dev/null +++ b/src/controls/ActionRow.qml @@ -0,0 +1,299 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.13 +import org.kde.kirigami 2.12 as Kirigami + +/** + * This is a row with actions that will trigger on swipe. It is recommended to use + * this class to provide swipe shortcuts for the user. + * + * Example usage for an action row: + * @code + * import QtQuick 2.12 + * import QtQuick.Controls 2.12 + * import org.kde.kirigami 2.12 as Kirigami + * + * ListView { + * anchors.fill: parent + * model: 10 + * delegate: ActionRow { + * width: parent.width + * leftAction: Action { + * text: "Copy" + * onTriggered: { + * print("Copy triggered") + * } + * } + * centerAction: Action { + * onTriggered: { + * print("Center triggered") + * } + * } + * rightAction: Action { + * text: "Delete" + * SwipeAction.foregroundColor: "white" + * SwipeAction.backgroundColor: "#bf0039" + * SwipeAction.isDelete: true + * icon.name: "edit-delete" + * onTriggered: { + * print("Delete triggered") + * } + * } + * } + * } + * @endcode + * + * @inherit QtQuick.Item + */ +Item { + id: root + clip: true + + height: Kirigami.Units.gridUnit*4 + + default property alias children: topRect.children + + NumberAnimation { + id: deleteAnimation + + target: root + property: "height" + duration: 200 + from: 64 + to: 0 + easing.type: Easing.InOutQuad + } + + + /** + * leftAction: QtQuick.Controls.Action + * If set, this property holds the action triggered when the left + * edge is revealed. + */ + property Action leftAction: null + /** + * centerAction: QtQuick.Controls.Action + * If set, this property holds the action that will be triggered + * when the row is interacted with. + * + * The row will show hover and click effects when this property is set. + */ + property Action centerAction: null + /** + * rightAction: QtQuick.Controls.Action + * If set, this property holds the action triggered when the right + * edge is revealed. + */ + property Action rightAction: null + + function defaultBg(color) { + if (!Qt.colorEqual(color, "transparent")) { + return color + } else { + return Kirigami.Theme.highlightColor + } + } + function defaultFg(color) { + if (!Qt.colorEqual(color, "transparent")) { + return color + } else { + return Kirigami.Theme.highlightedTextColor + } + } + + function calcTranslation() { + if (drag.translation.x == 0) { + return 0 + } + if (root.rightAction == null && drag.translation.x < 0) { + return 0 + } + if (root.leftAction == null && drag.translation.x > 0) { + return 0 + } + + let midway = (root.width / 2) + + if (drag.translation.x > -midway) { + return Math.min(drag.translation.x, midway) + } else if (drag.translation.x < midway) { + return Math.max(drag.translation.x, -midway) + } + } + + Row { + anchors.fill: parent + Rectangle { + width: root.width / 2 + height: root.height + Rectangle { + color: defaultBg(leftAction.Kirigami.SwipeAction.backgroundColor) + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + radius: width + height: width + width: drag.translation.x > root.width*(1/4) ? root.width : 0 + Behavior on width { + NumberAnimation { + duration: 400 + easing.type: Easing.InOutQuad + } + } + } + Column { + anchors.left: parent.left + anchors.leftMargin: Kirigami.Units.gridUnit + anchors.verticalCenter: parent.verticalCenter + Kirigami.Icon { + width: Kirigami.Units.iconSizes.smallMedium + height: Kirigami.Units.iconSizes.smallMedium + visible: leftAction.icon.name != "" + source: leftAction.icon.name + Kirigami.Theme.textColor: drag.translation.x > root.width*(1/4) ? defaultFg(leftAction.Kirigami.SwipeAction.foregroundColor) : Kirigami.Theme.textColor + anchors.horizontalCenter: parent.horizontalCenter + } + Text { + anchors.horizontalCenter: parent.horizontalCenter + color: drag.translation.x > root.width*(1/4) ? defaultFg(leftAction.Kirigami.SwipeAction.foregroundColor) : Kirigami.Theme.textColor + Behavior on color { + ColorAnimation { + duration: 400 + easing.type: Easing.InOutQuad + } + } + text: leftAction.text + } + } + color: Kirigami.Theme.backgroundColor + } + Rectangle { + width: root.width / 2 + height: root.height + Rectangle { + color: defaultBg(rightAction.Kirigami.SwipeAction.backgroundColor) + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + radius: width + height: width + width: drag.translation.x < -root.width*(1/4) ? root.width : 0 + Behavior on width { + NumberAnimation { + duration: 400 + easing.type: Easing.InOutQuad + } + } + } + Column { + anchors.right: parent.right + anchors.rightMargin: Kirigami.Units.gridUnit + anchors.verticalCenter: parent.verticalCenter + Kirigami.Icon { + width: Kirigami.Units.iconSizes.smallMedium + height: Kirigami.Units.iconSizes.smallMedium + source: rightAction.icon.name + visible: rightAction.icon.name != "" + Kirigami.Theme.textColor: drag.translation.x > root.width*(1/4) ? defaultFg(leftAction.Kirigami.SwipeAction.foregroundColor) : Kirigami.Theme.foregroundColor + anchors.horizontalCenter: parent.horizontalCenter + } + Text { + anchors.horizontalCenter: parent.horizontalCenter + color: drag.translation.x < -root.width*(1/4) ? defaultFg(rightAction.Kirigami.SwipeAction.foregroundColor) : Kirigami.Theme.foregroundColor + Behavior on color { + ColorAnimation { + duration: 400 + easing.type: Easing.InOutQuad + } + } + text: rightAction.text + } + } + + color: Kirigami.Theme.backgroundColor + } + } + + Rectangle { + id: topRect + + Kirigami.Theme.colorSet: Kirigami.Theme.View + color: Kirigami.Theme.backgroundColor + + Rectangle { + visible: root.centerAction != null + + anchors.fill: parent + color: Kirigami.Theme.highlightColor + opacity: { + return (topRectMouseArea.pressed) ? 0.3 : (topRectMouseArea.containsMouse ? 0.1 : 0); + } + Behavior on opacity { + OpacityAnimator { + duration: Kirigami.Units.longDuration + easing.type: Easing.InOutQuad + } + } + } + + anchors.fill: parent + transform: Translate { + id: translation + x: root.calcTranslation() + } + + MouseArea { + id: topRectMouseArea + + hoverEnabled: true + anchors.fill: parent + enabled: root.centerAction != null + onClicked: { + root.centerAction.trigger() + } + } + + DragHandler { + id: drag + onActiveChanged: { + if (!active) { + let del = false + if (drag.translation.x < -root.width*(1/4)) { + if (rightAction.Kirigami.SwipeAction.isDelete) { + del = true + } + rightAction.trigger() + } else if (drag.translation.x > root.width*(1/4)) { + if (leftAction.Kirigami.SwipeAction.isDelete) { + del = true + } + leftAction.trigger() + } + + if (del) { + deleteAnimation.restart() + } else { + fallBack.restart() + } + } else { + fallBack.stop() + } + } + } + } + Kirigami.Separator { + anchors { + bottom: parent.bottom + left: parent.left + right: parent.right + } + } + + SequentialAnimation { + id: fallBack + NumberAnimation { + target: translation + property: "x" + duration: 300 + to: 0 + easing.type: Easing.InOutQuad + } + } +} diff --git a/src/kirigamiplugin.cpp b/src/kirigamiplugin.cpp --- a/src/kirigamiplugin.cpp +++ b/src/kirigamiplugin.cpp @@ -30,6 +30,7 @@ #include "pagepool.h" #include "scenepositionattached.h" #include "wheelhandler.h" +#include "swipeaction.h" #include #include @@ -214,6 +215,10 @@ qmlRegisterType(uri, 2, 11, "PagePool"); qmlRegisterType(componentUrl(QStringLiteral("PagePoolAction.qml")), uri, 2, 11, "PagePoolAction"); + // 2.12 + qmlRegisterUncreatableType(uri, 2, 12, "SwipeAction", QStringLiteral("Cannot create objects of type SwipeAction.")); + qmlRegisterType(componentUrl(QStringLiteral("ActionRow.qml")), uri, 2, 12, "ActionRow"); + //TODO: remove qmlRegisterType(componentUrl(QStringLiteral("SwipeListItem2.qml")), uri, 2, 11, "SwipeListItem2"); diff --git a/src/swipeaction.h b/src/swipeaction.h new file mode 100644 --- /dev/null +++ b/src/swipeaction.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include + +class SwipeAction : public QObject +{ + Q_OBJECT + /** + * backgroundColor: QColor + * + * If set, this color is used by the action's radial fill to indicate + * when it will trigger on release. + */ + Q_PROPERTY(QColor backgroundColor MEMBER m_backgroundColor NOTIFY backgroundColorChanged) + + /** + * foregroundColor: QColor + * + * If set, this color is used by the action's text to indicate + * when it will trigger on release. + */ + Q_PROPERTY(QColor foregroundColor MEMBER m_foregroundColor NOTIFY foregroundColorChanged) + + /** + * isDelete: bool + * + * If set to true, this action will collapse on trigger. + */ + Q_PROPERTY(bool isDelete MEMBER m_isDelete NOTIFY isDeleteChanged) + +public: + explicit SwipeAction(QObject *parent = nullptr); + static SwipeAction *qmlAttachedProperties(QObject *object); + +private: + QColor m_backgroundColor; + QColor m_foregroundColor; + bool m_isDelete; + +Q_SIGNALS: + void backgroundColorChanged(); + void foregroundColorChanged(); + void isDeleteChanged(); +}; + +QML_DECLARE_TYPEINFO(SwipeAction, QML_HAS_ATTACHED_PROPERTIES) diff --git a/src/swipeaction.cpp b/src/swipeaction.cpp new file mode 100644 --- /dev/null +++ b/src/swipeaction.cpp @@ -0,0 +1,13 @@ +#include "swipeaction.h" + +SwipeAction::SwipeAction(QObject *parent) : QObject(parent) { + m_backgroundColor = QColor("transparent"); + m_foregroundColor = QColor("transparent"); + m_isDelete = false; +} + +SwipeAction* SwipeAction::qmlAttachedProperties(QObject *object) { + return new SwipeAction(object); +} + +#include "moc_swipeaction.cpp" \ No newline at end of file