From 2f437efcc8a7c8598daafa937bbd82129ffccac8 Mon Sep 17 00:00:00 2001 From: ck Date: Tue, 30 Jul 2024 11:07:10 +0800 Subject: [PATCH 1/3] feat: add ListView to support hover animations - item enter / leave animation - item hover changed animation - fix backgroundVisible not work --- chameleon/CMakeLists.txt | 1 + chameleon/ListView.qml | 9 ++ qt6/src/dtkdeclarative_qml.qrc | 1 + qt6/src/qml.cmake | 1 + qt6/src/qml/ItemDelegate.qml | 22 ++--- qt6/src/qml/ListView.qml | 165 +++++++++++++++++++++++++++++++++ 6 files changed, 188 insertions(+), 11 deletions(-) create mode 100644 chameleon/ListView.qml create mode 100644 qt6/src/qml/ListView.qml diff --git a/chameleon/CMakeLists.txt b/chameleon/CMakeLists.txt index e7b6ca9f9..fd2ad28e9 100644 --- a/chameleon/CMakeLists.txt +++ b/chameleon/CMakeLists.txt @@ -24,6 +24,7 @@ set(QML_FILES Frame.qml GroupBox.qml ItemDelegate.qml + ListView.qml MenuItem.qml Menu.qml MenuSeparator.qml diff --git a/chameleon/ListView.qml b/chameleon/ListView.qml new file mode 100644 index 000000000..43fe1d9c0 --- /dev/null +++ b/chameleon/ListView.qml @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +import org.deepin.dtk 1.0 as D + +D.ListView { + +} diff --git a/qt6/src/dtkdeclarative_qml.qrc b/qt6/src/dtkdeclarative_qml.qrc index fb83f85c9..899a09ac3 100644 --- a/qt6/src/dtkdeclarative_qml.qrc +++ b/qt6/src/dtkdeclarative_qml.qrc @@ -63,6 +63,7 @@ qml/private/ButtonPanel.qml qml/ActionButton.qml qml/ItemDelegate.qml + qml/ListView.qml qml/CheckDelegate.qml qml/TipsSlider.qml qml/SliderTipItem.qml diff --git a/qt6/src/qml.cmake b/qt6/src/qml.cmake index 998efbbfe..1fa8f897f 100644 --- a/qt6/src/qml.cmake +++ b/qt6/src/qml.cmake @@ -52,6 +52,7 @@ set(QML_DTK_CONTROLS "qml/ComboBox.qml" "qml/ActionButton.qml" "qml/ItemDelegate.qml" + "qml/ListView.qml" "qml/CheckDelegate.qml" "qml/TipsSlider.qml" "qml/SliderTipItem.qml" diff --git a/qt6/src/qml/ItemDelegate.qml b/qt6/src/qml/ItemDelegate.qml index 0a2acca4d..19df49245 100644 --- a/qt6/src/qml/ItemDelegate.qml +++ b/qt6/src/qml/ItemDelegate.qml @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2022-2024 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later @@ -7,6 +7,7 @@ import QtQuick.Templates as T import QtQuick.Layouts 1.11 import org.deepin.dtk 1.0 as D import org.deepin.dtk.style 1.0 as DS +import org.deepin.dtk.private 1.0 as P T.ItemDelegate { id: control @@ -35,7 +36,7 @@ T.ItemDelegate { spacing: DS.Style.control.spacing checkable: true autoExclusive: true - palette.windowText: checked && !control.cascadeSelected ? D.ColorSelector.checkedTextColor : undefined + palette.windowText: checked && !control.cascadeSelected && control.backgroundVisible ? D.ColorSelector.checkedTextColor : undefined D.DciIcon.mode: D.ColorSelector.controlState D.DciIcon.theme: D.ColorSelector.controlTheme @@ -83,6 +84,7 @@ T.ItemDelegate { } background: Item { + visible: backgroundVisible implicitWidth: DS.Style.itemDelegate.width implicitHeight: DS.Style.itemDelegate.height @@ -101,15 +103,13 @@ T.ItemDelegate { corners: control.corners } } + } - Loader { - anchors.fill: parent - active: !checked && control.backgroundVisible - sourceComponent: D.RoundRectangle { - color: DS.Style.itemDelegate.normalColor - radius: DS.Style.control.radius - corners: control.corners - } - } + onHoveredChanged: { + if (checked || control.cascadeSelected || !backgroundVisible) + return + + if (ListView.view) + ListView.view.setHoverItem(control.hovered ? control : null) } } diff --git a/qt6/src/qml/ListView.qml b/qt6/src/qml/ListView.qml new file mode 100644 index 000000000..d69525310 --- /dev/null +++ b/qt6/src/qml/ListView.qml @@ -0,0 +1,165 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +import QtQuick 2.11 +import QtQuick.Templates as T +import org.deepin.dtk.style 1.0 as DS +import org.deepin.dtk.private 1.0 as P + +ListView { + id: control + property int duration: 100 + property bool bgVisible: false + property Item hoveredItem + + onContentXChanged: { + if (hoveredItem && hoveredItem.hovered && background) + background.x = hoveredItem.x - contentX + } + + onContentYChanged: { + if (hoveredItem && hoveredItem.hovered && background) + background.y = hoveredItem.y - contentY + } + + function setHoverItem(item) { + if (item) { + hoveredItem = item + // if hover from null to item do not show animation + bgVisible = (background.x <= 0 && background.y <= 0) ? false : hoveredItem.hovered + background.x = hoveredItem.x - contentX + background.y = hoveredItem.y - contentY + background.width = hoveredItem.implicitWidth + background.height = hoveredItem.implicitHeight + bgVisible = hoveredItem.hovered + control.state = bgVisible ? "Visible" : "Invisible" + } else { + hideTimer.start() + } + } + + // ItemDelegate hover item0 ==> item1, add timer ignore [item0.unhovered] + // item0.hovered, item0.unhovered, item1.hovered + Timer { + id: hideTimer + interval: control.duration + onTriggered: { + bgVisible = hoveredItem ? hoveredItem.hovered : false + control.state = bgVisible ? "Visible" : "Invisible" + } + } + + // visible animation + states: [ + State{ + name: "Visible" + PropertyChanges { target: background; opacity: 1.0 } + PropertyChanges { target: background; scale: 1.0 } + PropertyChanges { target: background; visible: true } + }, + State{ + name:"Invisible" + PropertyChanges { target: background; opacity: 0.0 } + PropertyChanges { target: background; scale: 0.5 } + PropertyChanges { target: background; visible: false } + } + ] + + transitions: [ + Transition { + from: "Visible" + to: "Invisible" + + SequentialAnimation { + ParallelAnimation { + NumberAnimation { + target: background + property: "opacity" + duration: control.duration + easing.type: Easing.InOutQuad + } + NumberAnimation { + target: background + property: "scale" + duration: control.duration + easing.type: Easing.InOutQuad + } + } + NumberAnimation { + target: background + property: "visible" + duration: 0 + } + } + }, + Transition { + from: "Invisible" + to: "Visible" + SequentialAnimation { + NumberAnimation { + target: background + property: "visible" + duration: 0 + } + ParallelAnimation { + NumberAnimation { + target: background + property: "opacity" + duration: control.duration + easing.type: Easing.InOutQuad + } + NumberAnimation { + target: background + property: "scale" + duration: control.duration + easing.type: Easing.InOutQuad + } + } + } + } + ] + + Item { + id: background + implicitWidth: DS.Style.itemDelegate.width + implicitHeight: DS.Style.itemDelegate.height + z: -1 + + Loader { + anchors.fill: parent + active: hoveredItem + sourceComponent: P.ButtonPanel { + button: hoveredItem + outsideBorderColor: null + visible: hoveredItem && !hoveredItem.checked && hoveredItem.enabled + } + } + + onVisibleChanged: { + if (!visible) { + // ensure do not show animation when hoveredItem from null to item + background.x = -1 + background.y = -1 + } + } + + // move and resize animation + Behavior on x { + enabled: bgVisible + NumberAnimation { duration: control.duration } + } + Behavior on y { + enabled: bgVisible + NumberAnimation { duration: control.duration } + } + Behavior on width { + enabled: bgVisible + NumberAnimation { duration: control.duration } + } + Behavior on height { + enabled: bgVisible + NumberAnimation { duration: control.duration } + } + } +} From fbc720dd46cd7126024dcfe1a9490f47f4507e16 Mon Sep 17 00:00:00 2001 From: ck Date: Wed, 31 Jul 2024 13:12:51 +0800 Subject: [PATCH 2/3] feat: CheckDelegate animation listview check animation(dci) --- qt6/src/qml/CheckDelegate.qml | 25 +++++++++++++++++++++---- src/dtkdeclarative_assets.qrc | 2 ++ src/icons/bloom/item_checked.dci | Bin 0 -> 10986 bytes src/icons/bloom/item_unchecked.dci | Bin 0 -> 4432 bytes 4 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 src/icons/bloom/item_checked.dci create mode 100644 src/icons/bloom/item_unchecked.dci diff --git a/qt6/src/qml/CheckDelegate.qml b/qt6/src/qml/CheckDelegate.qml index 8db95ae2c..ec1cb3801 100644 --- a/qt6/src/qml/CheckDelegate.qml +++ b/qt6/src/qml/CheckDelegate.qml @@ -12,6 +12,8 @@ T.CheckDelegate { id: control property Component content property D.Palette backgroundColor: DS.Style.itemDelegate.checkBackgroundColor + property string indicatorIcon: control.checkState === Qt.Unchecked ? "item_unchecked" : "item_checked" + property bool indicatorVisible: control.checked implicitWidth: DS.Style.control.implicitWidth(control) implicitHeight: DS.Style.control.implicitHeight(control) @@ -26,15 +28,22 @@ T.CheckDelegate { indicator: Loader { x: control.mirrored ? control.leftPadding : control.width - width - control.rightPadding y: control.topPadding + (control.availableHeight - height) / 2 - active: control.checked + active: indicatorVisible sourceComponent: D.DciIcon { palette: control.D.DTK.makeIconPalette(control.palette) mode: control.D.ColorSelector.controlState theme: control.D.ColorSelector.controlTheme - name: "menu_select" + name: indicatorIcon sourceSize: Qt.size(DS.Style.itemDelegate.checkIndicatorIconSize, DS.Style.itemDelegate.checkIndicatorIconSize) fallbackToQIcon: false + onNameChanged: { + play(D.DTK.NormalState); + } + Component.onCompleted: { + if (indicatorVisible) + play(D.DTK.NormalState); + } } } @@ -58,12 +67,12 @@ T.CheckDelegate { } } - background: Item { + background: Control { implicitWidth: DS.Style.itemDelegate.width implicitHeight: DS.Style.itemDelegate.height Rectangle { anchors.fill: parent - visible: !checked + visible: !checked && !control.ListView.view color: control.D.ColorSelector.backgroundColor radius: DS.Style.control.radius } @@ -74,4 +83,12 @@ T.CheckDelegate { radius: DS.Style.control.radius } } + + onHoveredChanged: { + if (checked || !ListView.view) + return + + if (ListView.view) + ListView.view.setHoverItem(control.hovered ? control : null) + } } diff --git a/src/dtkdeclarative_assets.qrc b/src/dtkdeclarative_assets.qrc index e4d3ec914..689488046 100644 --- a/src/dtkdeclarative_assets.qrc +++ b/src/dtkdeclarative_assets.qrc @@ -89,6 +89,8 @@ icons/bloom/list_delete.dci icons/bloom/menu_arrow.dci icons/bloom/menu_select.dci + icons/bloom/item_checked.dci + icons/bloom/item_unchecked.dci icons/bloom/window_sidebar.dci icons/bloom/action_add.dci icons/bloom/radio_checked.dci diff --git a/src/icons/bloom/item_checked.dci b/src/icons/bloom/item_checked.dci new file mode 100644 index 0000000000000000000000000000000000000000..e38bb10313a849ec879dcb4d029c8259df4959a6 GIT binary patch literal 10986 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 LkGAperfvKW(%h#e literal 0 HcmV?d00001 diff --git a/src/icons/bloom/item_unchecked.dci b/src/icons/bloom/item_unchecked.dci new file mode 100644 index 0000000000000000000000000000000000000000..4da8717c0d11d3e9089a2e408e0ae0c84adb24db GIT binary patch literal 4432 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 Date: Fri, 2 Aug 2024 14:02:38 +0800 Subject: [PATCH 3/3] feat: dnd multi selected item add DragItemsImage to Generate a thumbnail of dragged items --- qt6/src/qml.cmake | 1 + qt6/src/qml/DragItemsImage.qml | 79 ++++++++++++++++++++++++++++++++++ qt6/src/qml/ItemDelegate.qml | 61 ++++++++++++++++++++++++-- qt6/src/qml/ListView.qml | 51 +++++++++++++++++----- 4 files changed, 177 insertions(+), 15 deletions(-) create mode 100644 qt6/src/qml/DragItemsImage.qml diff --git a/qt6/src/qml.cmake b/qt6/src/qml.cmake index 1fa8f897f..0a40268ad 100644 --- a/qt6/src/qml.cmake +++ b/qt6/src/qml.cmake @@ -99,6 +99,7 @@ set(QML_DTK_CONTROLS "qml/PlaceholderText.qml" "qml/ControlGroup.qml" "qml/ControlGroupItem.qml" + "qml/DragItemsImage.qml" ) foreach(QML_FILE ${QML_DTK_CONTROLS}) diff --git a/qt6/src/qml/DragItemsImage.qml b/qt6/src/qml/DragItemsImage.qml new file mode 100644 index 000000000..934b362b0 --- /dev/null +++ b/qt6/src/qml/DragItemsImage.qml @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +import QtQuick 2.15 + +Item { + id: control + property list items + + signal aboutToGrabToImage(Item item) + signal grabToImageFinished(Item item) + + onItemsChanged: function updateGeomotry() { + var w = 0 + var h = 0 + for (var i = 0; i < control.items.length; ++i) { + let item = control.items[i] + w = Math.max(w, item.width) + h = Math.max(h, item.height) + } + control.width = w + 30 + control.height = h * 2 + 30 + } + + function addItem(item) { + if (control.items.include(item)) + return + + items.push(item) + } + + function removeItem(item) { + if (!control.items.include(item)) + return + + var index = control.items.indexOf(item); + if (index !== -1) { + control.items.splice(index, 1); + } + } + + Repeater { + model: control.items.length + delegate: Image { + id: img + anchors.centerIn: parent + antialiasing: true + rotation: index ? (index % 2 === 0 ? 10 : -10) : 0 + opacity: (1 - index * 0.2) + z: -index + + Component.onCompleted: { + let item = control.items[index] + if (!item) + return + + // item.dragActive = true + aboutToGrabToImage(item) + item.grabToImage(function(result) { + img.source = result.url + grabToImageFinished(item) + }) + } + } + } + + RoundButton { + id: number + checked: true + anchors.right: dragItem.right + anchors.top: dragItem.top + anchors.margins: 8 + implicitWidth: 24 + implicitHeight: 24 + text: control.items.length > 1 ? control.items.length : "" + opacity: control.items.length > 1 ? 1 : 0 + } +} diff --git a/qt6/src/qml/ItemDelegate.qml b/qt6/src/qml/ItemDelegate.qml index 19df49245..f8c5a205c 100644 --- a/qt6/src/qml/ItemDelegate.qml +++ b/qt6/src/qml/ItemDelegate.qml @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later -import QtQuick 2.11 +import QtQuick import QtQuick.Templates as T import QtQuick.Layouts 1.11 import org.deepin.dtk 1.0 as D @@ -18,6 +18,12 @@ T.ItemDelegate { property Component content property D.Palette checkedTextColor: DS.Style.checkedButton.text property int corners: D.RoundRectangle.TopLeftCorner | D.RoundRectangle.TopRightCorner | D.RoundRectangle.BottomLeftCorner | D.RoundRectangle.BottomRightCorner + property bool dragActive: false + // drag + property bool enableDrag: false + Drag.mimeData: { "text/plain": control.text } + Drag.dragType: Drag.Automatic + function getCornersForBackground(index, count) { if (count <= 1) return D.RoundRectangle.TopLeftCorner | D.RoundRectangle.TopRightCorner | D.RoundRectangle.BottomLeftCorner | D.RoundRectangle.BottomRightCorner @@ -36,7 +42,7 @@ T.ItemDelegate { spacing: DS.Style.control.spacing checkable: true autoExclusive: true - palette.windowText: checked && !control.cascadeSelected && control.backgroundVisible ? D.ColorSelector.checkedTextColor : undefined + palette.windowText: checked && !control.cascadeSelected && control.backgroundVisible && !dragActive? D.ColorSelector.checkedTextColor : undefined D.DciIcon.mode: D.ColorSelector.controlState D.DciIcon.theme: D.ColorSelector.controlTheme @@ -90,7 +96,7 @@ T.ItemDelegate { Loader { anchors.fill: parent - active: checked && !control.cascadeSelected + active: checked && !control.cascadeSelected && !dragActive sourceComponent: HighlightPanel {} } @@ -103,13 +109,60 @@ T.ItemDelegate { corners: control.corners } } + + Loader { + anchors.fill: parent + active: !control.ListView.view && !checked && control.backgroundVisible + sourceComponent: D.RoundRectangle { + color: DS.Style.itemDelegate.normalColor + radius: DS.Style.control.radius + corners: control.corners + } + } + + Loader { + anchors.fill: parent + active: dragActive + sourceComponent: Rectangle { + border.color: Qt.rgba(0, 0, 0, 0.09) + radius: DS.Style.control.radius + } + } + } + + DragHandler { + id: dragHandler + enabled: enableDrag + onActiveChanged: { + if (active) { + let dragItem = control.ListView.view.dragItem + if (!dragItem) + return + + let md = JSON.stringify(dragItem.Drag.mimeData) + + if (md.length > 2) // '{}' + control.Drag.mimeData = dragItem.Drag.mimeData + + dragItem.grabToImage(function(result) { + control.Drag.imageSource = result.url; + control.Drag.hotSpot = Qt.point(dragItem.width / 2, dragItem.height / 2) + control.Drag.active = true + }) + } + } } onHoveredChanged: { - if (checked || control.cascadeSelected || !backgroundVisible) + if (checked || control.cascadeSelected || !backgroundVisible || dragActive) return if (ListView.view) ListView.view.setHoverItem(control.hovered ? control : null) } + + onCheckedChanged: { + if (ListView.view) + ListView.view.updateCheckedItems() + } } diff --git a/qt6/src/qml/ListView.qml b/qt6/src/qml/ListView.qml index d69525310..562839d36 100644 --- a/qt6/src/qml/ListView.qml +++ b/qt6/src/qml/ListView.qml @@ -2,8 +2,9 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later -import QtQuick 2.11 +import QtQuick 2.15 import QtQuick.Templates as T +import org.deepin.dtk 1.0 as D import org.deepin.dtk.style 1.0 as DS import org.deepin.dtk.private 1.0 as P @@ -12,16 +13,8 @@ ListView { property int duration: 100 property bool bgVisible: false property Item hoveredItem - - onContentXChanged: { - if (hoveredItem && hoveredItem.hovered && background) - background.x = hoveredItem.x - contentX - } - - onContentYChanged: { - if (hoveredItem && hoveredItem.hovered && background) - background.y = hoveredItem.y - contentY - } + property list checkedItems + property alias dragItem: dragItem function setHoverItem(item) { if (item) { @@ -39,6 +32,42 @@ ListView { } } + function updateCheckedItems() { + var items = [] + for (var i = 0; i < count; ++i) { + let item = itemAtIndex(i) + if (item && item.checked) { + items.push(item) + } + } + + checkedItems = items + } + + DragItemsImage { + id: dragItem + items: checkedItems + visible: Drag.active + // Drag.mimeData: {"text/plain": ""} + + onAboutToGrabToImage: function(item) { + item.dragActive = true + } + onGrabToImageFinished: function(item) { + item.dragActive = false + } + } + + onContentXChanged: { + if (hoveredItem && hoveredItem.hovered && background) + background.x = hoveredItem.x - contentX + } + + onContentYChanged: { + if (hoveredItem && hoveredItem.hovered && background) + background.y = hoveredItem.y - contentY + } + // ItemDelegate hover item0 ==> item1, add timer ignore [item0.unhovered] // item0.hovered, item0.unhovered, item1.hovered Timer {