Skip to content

Commit 0848d6b

Browse files
wjyrichdeepin-bot[bot]
authored andcommitted
feat: enhance drag-and-drop visual feedback and folder icon animation
feat: 增强拖放视觉反馈和文件夹图标动画效果 提供拖动应用到文件夹的动画效果,实现应用原地缩小以及位移到相应的文件夹的应用位置, 采用方案为获取鼠标位置大小,然后采用hotspot的坐标偏移,获取应用图标中心点,然后转为icon坐标系的坐标图标应该放置的应用图标中心点,从而解决由于缩放图标会产生的位置偏移问题。 另外去除 girdview 是由于计算位置正确,但是存在着显示问题却会偏移像素级位置,所以去除。 采用dragAndfolderBackground 为 文件夹的背景, 也是对应的drag时候的图标的背景。 PMS: BUG-288931 BUG-315679
1 parent f575fcc commit 0848d6b

4 files changed

Lines changed: 201 additions & 44 deletions

File tree

qml/FolderGridViewPopup.qml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -558,9 +558,15 @@ Popup {
558558
}
559559

560560
component DelegateDropArea: DropArea {
561+
property bool isDragHover: false
562+
561563
onEntered: function(drag) {
562564
root.onDragEnter(this)
563-
folderDragApplyTimer.dragId = drag.getDataAsString("text/x-dde-launcher-dnd-desktopId")
565+
let dragId = drag.getDataAsString("text/x-dde-launcher-dnd-desktopId")
566+
if (dragId !== model.desktopId) {
567+
isDragHover = true
568+
}
569+
folderDragApplyTimer.dragId = dragId
564570
folderDragApplyTimer.restart()
565571
}
566572
onPositionChanged: function(drag) {
@@ -575,6 +581,7 @@ Popup {
575581
}
576582
}
577583
onExited: {
584+
isDragHover = false
578585
root.onDragExit(this)
579586
folderDragApplyTimer.stop()
580587
folderDragApplyTimer.dragId = ""
@@ -583,6 +590,7 @@ Popup {
583590
root.onDragExit(this)
584591
}
585592
onDropped: function(drop) {
593+
isDragHover = false
586594
let dragId = drop.getDataAsString("text/x-dde-launcher-dnd-desktopId")
587595
if (dragId === "") {
588596
return
@@ -636,6 +644,7 @@ Popup {
636644
id: innerItem
637645
anchors.fill: parent
638646
dndEnabled: true
647+
isDragHover: false
639648
displayFont: isWindowedMode ? DTK.fontManager.t9 : DTK.fontManager.t6
640649
Drag.mimeData: Helper.generateDragMimeData(model.desktopId)
641650
visible: dndItem.currentlyDraggedId !== model.desktopId

qml/FullscreenFrame.qml

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
1+
// SPDX-FileCopyrightText: 2023-2026 UnionTech Software Technology Co., Ltd.
22
//
33
// SPDX-License-Identifier: GPL-3.0-or-later
44

@@ -33,7 +33,18 @@ InputEventItem {
3333
// ----------- Drag and Drop related functions START -----------
3434
Label {
3535
property string currentlyDraggedId
36-
36+
property string currentlyDraggedIconName
37+
38+
property bool mergeAnimPending: false
39+
//被拖拽图标
40+
property string mergeAnimTargetIcon: ""
41+
//放下的图标或文件夹
42+
property string mergeAnimTargetIcon2: ""
43+
// 鼠标松手位置(窗口坐标)
44+
property real mergeAnimStartX: 0
45+
property real mergeAnimStartY: 0
46+
47+
property real mergeSize: 0
3748
signal dragEnded()
3849

3950
id: dndItem
@@ -45,6 +56,7 @@ InputEventItem {
4556
text = "Dragging " + currentlyDraggedId
4657
} else {
4758
currentlyDraggedId = ""
59+
currentlyDraggedIconName = ""
4860
dragEnded()
4961
}
5062
}
@@ -547,21 +559,29 @@ InputEventItem {
547559
delegate: DropArea {
548560
Keys.forwardTo: [iconItemDelegate]
549561

562+
property bool isDragHover: false
563+
550564
visible: !folderGridViewPopup.visible || folderGridViewPopup.currentFolderId !== Number(model.desktopId.replace("internal/folders/", ""))
551565
width: gridViewContainer.cellWidth
552566
height: gridViewContainer.cellHeight
553567
onEntered: function (drag) {
554568
if (folderGridViewPopup.opened) {
555569
folderGridViewPopup.close()
556570
}
557-
dndDropEnterTimer.dragId = drag.getDataAsString("text/x-dde-launcher-dnd-desktopId")
571+
let dragId = drag.getDataAsString("text/x-dde-launcher-dnd-desktopId")
572+
if (dragId !== model.desktopId) {
573+
isDragHover = true
574+
}
575+
dndDropEnterTimer.dragId = dragId
558576
dndDropEnterTimer.restart()
559577
}
560578
onExited: {
579+
isDragHover = false
561580
dndDropEnterTimer.stop()
562581
dndDropEnterTimer.dragId = ""
563582
}
564583
onDropped: function (drop) {
584+
isDragHover = false
565585
dndDropEnterTimer.stop()
566586
dndDropEnterTimer.dragId = ""
567587
let dragId = drop.getDataAsString("text/x-dde-launcher-dnd-desktopId")
@@ -572,6 +592,15 @@ InputEventItem {
572592
} else if (drop.x > (width - sideOpPadding)) {
573593
op = 1
574594
}
595+
if (op === 0) {
596+
dndItem.mergeAnimTargetIcon = dndItem.currentlyDraggedIconName
597+
dndItem.mergeAnimTargetIcon2 = !folderIcons ? iconItemDelegate.iconSource : ""
598+
let cursorScene = mapToItem(null, drop.x, drop.y)
599+
let hs = dndItem.Drag.hotSpot
600+
dndItem.mergeAnimStartX = cursorScene.x - hs.x + dndItem.mergeSize / 2
601+
dndItem.mergeAnimStartY = cursorScene.y - hs.y + dndItem.mergeSize / 2
602+
dndItem.mergeAnimPending = true
603+
}
575604
dropOnItem(dragId, model.desktopId, op)
576605
proxyModel.sort(0)
577606
}
@@ -606,6 +635,7 @@ InputEventItem {
606635
}
607636
enabled: !folderGridViewPopup.visible
608637
dndEnabled: !folderGridViewPopup.opened
638+
isDragHover: parent.isDragHover
609639
Drag.mimeData: Helper.generateDragMimeData(model.desktopId)
610640
visible: dndItem.currentlyDraggedId !== model.desktopId
611641
iconSource: (iconName && iconName !== "") ? iconName : "application-x-desktop"

qml/IconItemDelegate.qml

Lines changed: 147 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,11 @@ Control {
2828

2929
property string iconSource
3030
property bool dndEnabled: false
31+
property bool isDragHover: false
3132
readonly property bool isWindowedMode: LauncherController.currentFrame === "WindowedFrame"
3233
property alias displayFont: iconItemLabel.font
3334
property real iconScaleFactor: 1.0
35+
property bool iconIntroAnimRunning: false
3436

3537
Accessible.name: iconItemLabel.text
3638

@@ -53,6 +55,7 @@ Control {
5355
}
5456

5557
contentItem: Button {
58+
hoverEnabled: !root.iconIntroAnimRunning
5659
focusPolicy: Qt.NoFocus
5760
ColorSelector.pressed: false
5861
ColorSelector.family: D.Palette.CrystalColor
@@ -67,10 +70,42 @@ Control {
6770
}
6871

6972
Item {
73+
id: iconContainer
7074
width: parent.width / 2
7175
height: width
7276
anchors.horizontalCenter: parent.horizontalCenter
7377

78+
Rectangle {
79+
id: dragAndfolderBackground
80+
visible: root.icons !== undefined || (root.isDragHover && !isWindowedMode)
81+
opacity: root.icons !== undefined || (root.isDragHover && !isWindowedMode) ? 1 : 0
82+
scale: (root.isDragHover && !isWindowedMode) ? 1.2 : 1
83+
color: "#26FFFFFF"
84+
Behavior on opacity {
85+
NumberAnimation { duration: 200; easing.type: Easing.OutQuad }
86+
}
87+
Behavior on scale {
88+
NumberAnimation { duration: 200; easing.type: Easing.OutCubic }
89+
}
90+
anchors.fill: parent
91+
radius: 12
92+
93+
NumberAnimation on scale {
94+
id: ininAni
95+
running: false
96+
from: 1.2
97+
to: 1
98+
duration: 200
99+
easing.type: Easing.OutCubic
100+
}
101+
102+
Component.onCompleted: {
103+
if (root.icons !== undefined && dndItem.mergeAnimTargetIcon && dndItem.mergeAnimTargetIcon2) {
104+
ininAni.start()
105+
}
106+
}
107+
}
108+
74109
Loader {
75110
id: iconLoader
76111
anchors.fill: parent
@@ -91,8 +126,10 @@ Control {
91126
// Item will be hidden by checking the dndItem.currentlyDraggedId property. We assign the value
92127
// to that property here
93128
dndItem.currentlyDraggedId = target.Drag.mimeData["text/x-dde-launcher-dnd-desktopId"]
129+
dndItem.currentlyDraggedIconName = root.iconSource
94130
dndItem.Drag.hotSpot = target.Drag.hotSpot
95131
dndItem.Drag.mimeData = target.Drag.mimeData
132+
dndItem.mergeSize = Math.min(iconLoader.width, iconLoader.height)
96133

97134
iconLoader.grabToImage(function(result) {
98135
dndItem.Drag.imageSource = result.url;
@@ -118,50 +155,120 @@ Control {
118155
Component {
119156
id: folderComponent
120157

121-
Rectangle {
158+
Item {
159+
id: iconItem
122160
anchors.fill: parent
123-
color: "#26FFFFFF"
124-
radius: 12
125-
126-
GridLayout {
127-
id: folderGrid
128-
anchors.fill: parent
129-
rows: 2
130-
columns: 2
131-
anchors.margins: 8
132-
columnSpacing: 8
133-
rowSpacing: 8
134-
135-
Repeater {
136-
model: icons
137-
138-
DciIcon {
139-
Layout.fillHeight: true
140-
Layout.fillWidth: true
141-
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
142-
143-
// 添加最大高度限制,确保图标高度一致
144-
Layout.maximumHeight: Math.max(0, parent.height / 2 - folderGrid.rowSpacing / 2)
145-
146-
name: modelData
147-
sourceSize: Qt.size(root.maxIconSizeInFolder, root.maxIconSizeInFolder)
148-
scale: (parent.width / 2 / root.maxIconSizeInFolder) * root.iconScaleFactor
149-
palette: DTK.makeIconPalette(root.palette)
150-
theme: ApplicationHelper.DarkType
161+
property real maxIconCount: 2
162+
property real spacing: 8
163+
property real itemWidth: (width - ((maxIconCount + 1) * spacing)) / 2
164+
property real itemHeight: (height - ((maxIconCount + 1) * spacing)) / maxIconCount
165+
166+
function getItemX(index) {
167+
let col = index % maxIconCount
168+
let ItemX = (col + 1) * spacing + col * itemWidth
169+
170+
return ItemX
171+
}
172+
173+
function getItemY(index) {
174+
let row = Math.floor(index / maxIconCount)
175+
let ItemY = (row + 1) * spacing + row * itemHeight
176+
return ItemY
177+
}
178+
Repeater {
179+
model: icons
180+
181+
DciIcon {
182+
id: folderIcon
183+
x: iconItem.getItemX(index)
184+
y: iconItem.getItemY(index)
185+
186+
width: iconItem.itemWidth
187+
height: iconItem.itemHeight
188+
189+
name: modelData
190+
sourceSize: Qt.size(root.maxIconSizeInFolder, root.maxIconSizeInFolder)
191+
scale: (itemWidth / root.maxIconSizeInFolder) * root.iconScaleFactor
192+
193+
property real introScale: 1.0
194+
195+
palette: DTK.makeIconPalette(root.palette)
196+
theme: ApplicationHelper.DarkType
197+
198+
// 位移动画属性
199+
property real iconCenterX: 0
200+
property real iconCenterY: 0
201+
ParallelAnimation {
202+
id: iconIntroAnim
203+
onStarted: root.iconIntroAnimRunning = true
204+
205+
NumberAnimation {
206+
target: folderIcon
207+
property: "scale"
208+
from: folderIcon.introScale
209+
to: (itemWidth / root.maxIconSizeInFolder) * root.iconScaleFactor
210+
duration: 400
211+
easing.type: Easing.OutExpo
212+
}
213+
NumberAnimation {
214+
target: folderIcon
215+
property: "x"
216+
from: folderIcon.iconCenterX; to: iconItem.getItemX(index)
217+
duration: 400
218+
easing.type: Easing.OutExpo
219+
}
220+
NumberAnimation {
221+
target: folderIcon
222+
property: "y"
223+
from: folderIcon.iconCenterY; to: iconItem.getItemY(index)
224+
duration: 400
225+
easing.type: Easing.OutExpo
226+
}
227+
228+
onFinished: {
229+
root.iconIntroAnimRunning = false
230+
dndItem.mergeAnimPending = false
231+
dndItem.mergeAnimTargetIcon = ""
232+
dndItem.mergeAnimTargetIcon2 = ""
233+
}
234+
}
235+
236+
Component.onCompleted: {
237+
if (dndItem.mergeAnimPending
238+
&& modelData === dndItem.mergeAnimTargetIcon) {
239+
folderIcon.visible = false
240+
Qt.callLater(function() {
241+
let localPos = iconItem.mapFromItem(null,
242+
dndItem.mergeAnimStartX, dndItem.mergeAnimStartY)
243+
folderIcon.iconCenterX = localPos.x - folderIcon.width / 2
244+
folderIcon.iconCenterY = localPos.y - folderIcon.height / 2
245+
folderIcon.introScale = (iconContainer.width / root.maxIconSizeInFolder) * root.iconScaleFactor
246+
folderIcon.visible = true
247+
iconIntroAnim.start()
248+
})
249+
} else if (dndItem.mergeAnimPending
250+
&& modelData === dndItem.mergeAnimTargetIcon2) {
251+
Qt.callLater(function() {
252+
folderIcon.iconCenterX = iconContainer.width / 2 - folderIcon.width / 2
253+
folderIcon.iconCenterY = iconContainer.height / 2 - folderIcon.height / 2
254+
folderIcon.introScale = (iconContainer.width / root.maxIconSizeInFolder) * root.iconScaleFactor
255+
iconIntroAnim.start()
256+
})
257+
}
151258
}
152259
}
260+
}
153261

154-
Repeater {
155-
model: 4 - icons.length
262+
Repeater {
263+
model: 4 - icons.length
156264

157-
Item {
158-
Layout.fillHeight: true
159-
Layout.fillWidth: true
160-
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
265+
Item {
266+
Layout.fillHeight: true
267+
Layout.fillWidth: true
268+
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
161269

162-
width: parent.width / 2
163-
height: parent.height / 2
164-
}
270+
width: parent.width / 2
271+
height: parent.height / 2
165272
}
166273
}
167274
}
@@ -175,7 +282,7 @@ Control {
175282
anchors.fill: parent
176283
name: iconSource
177284
sourceSize: Qt.size(root.maxIconSize, root.maxIconSize)
178-
scale: (parent.width / root.maxIconSize) * root.iconScaleFactor
285+
scale: (iconContainer.width / root.maxIconSize) * root.iconScaleFactor
179286
palette: DTK.makeIconPalette(root.palette)
180287
theme: ApplicationHelper.DarkType
181288
fillMode: Image.PreserveAspectFit
@@ -193,6 +300,7 @@ Control {
193300
property bool singleRow: font.pixelSize > (isWindowedMode ? Helper.windowed.doubleRowMaxFontSize : Helper.fullscreen.doubleRowMaxFontSize)
194301
property bool isNewlyInstalled: model.lastLaunchedTime === 0 && model.installedTime !== 0
195302
id: iconItemLabel
303+
visible: !root.isDragHover
196304
text: isNewlyInstalled ? ("<font color='#669DFF' size='1' style='text-shadow: 0 0 1px rgba(255,255,255,0.1)'>●</font>&nbsp;&nbsp;" + root.text) : root.text
197305
textFormat: isNewlyInstalled ? Text.StyledText : Text.PlainText
198306
width: parent.width

0 commit comments

Comments
 (0)