on xenial help sign in
initial take on drag and drop and quicklists
commite7019dd08eceb71404218d171d962e98f976d74f authorMichael Zanetti <michael.zanetti@canonical.com> dateMon 22 Jul 2013, 13:11 (13 years ago) parentaf0265e
4 files changed+291−154
Launcher/Launcher.qml+1−1 Launcher/LauncherDelegate.qml+96−145 Launcher/LauncherPanel.qml+175−6 plugins/Unity/Launcher/launchermodel.cpp+19−2
Launcher/Launcher.qml+1−1
@@ -74,7 +74,7 @@ Item {
7474 interval: 5000
7575 onTriggered: {
7676 if (!panel.moving) {
77− root.state = ""
77+// root.state = ""
7878 } else {
7979 dismissTimer.restart()
8080 }
Launcher/LauncherDelegate.qml+96−145
@@ -22,24 +22,109 @@ Item {
2222
2323 property string iconName
2424
25− property real angle: 0
2625 property bool highlighted: false
27− property real offset: 0
28− property alias brightness: transformEffect.brightness
29− property real itemOpacity: 1
30−
3126 property real maxAngle: 0
3227 property bool inverted: false
3328
3429 readonly property int effectiveHeight: Math.cos(angle * Math.PI / 180) * height
3530 readonly property real foldedHeight: Math.cos(maxAngle * Math.PI / 180) * height
3631
37− property bool dragging:false
38−
3932 signal clicked()
4033 signal longtap()
4134 signal released()
4235
36+ // The angle used for rotating
37+ property real angle: {
38+ // First/last items are special
39+ if (index == 0 || index == priv.listView.count-1) {
40+ if (priv.distanceFromEdge < 0) {
41+ // proportion equation: distanceFromTopEdge : angle = totalUnfoldedHeight/2 : maxAngle
42+ return Math.max(-maxAngle, priv.distanceFromEdge * maxAngle / (priv.listView.foldingAreaHeight)) * priv.orientationFlag
43+ }
44+ return 0; // Don't fold first/last item as long as inside the view
45+ }
46+
47+ // Are we in the already completely outside the flickable? Fold for the last 5 degrees
48+ if (priv.distanceFromEdge < 0) {
49+ // proportion equation: -distanceFromTopEdge : angle = totalUnfoldedHeight : 5
50+ return Math.max(-maxAngle, (priv.distanceFromEdge * 5 / priv.listView.foldingAreaHeight) - (maxAngle-5)) * priv.orientationFlag
51+ }
52+
53+ // We are overlapping with the folding area, fold the icon to maxAngle - 5 degrees
54+ if (priv.overlapWithFoldingArea > 0) {
55+ // proportion equation: overlap: totalHeight = angle : (maxAngle - 5)
56+ return -priv.overlapWithFoldingArea * (maxAngle -5) / priv.listView.foldingAreaHeight * priv.orientationFlag;
57+ }
58+ return 0;
59+ }
60+
61+ // This is the offset that keeps the items inside the panel
62+ property real offset: {
63+ // First/last items are special
64+ if (index == 0 || index == priv.listView.count-1) {
65+ // Just keep them bound to the edges in case they're outside of the visible area
66+ if (priv.distanceFromEdge < 0) {
67+ return (-priv.distanceFromEdge - (height - effectiveHeight)) * priv.orientationFlag;
68+ }
69+ return 0;
70+ }
71+
72+ // Are we already completely outside the flickable? Stop the icon here.
73+ if (priv.distanceFromEdge < -priv.totalUnfoldedHeight) {
74+ return (-priv.distanceFromEdge - (root.height - effectiveHeight)) * priv.orientationFlag;
75+ }
76+
77+ // We're touching the edge, move slower than the actual flicking speed.
78+ if (priv.distanceFromEdge < 0) {
79+ return (Math.abs(priv.distanceFromEdge) * priv.totalEffectiveHeight / priv.totalUnfoldedHeight) * priv.orientationFlag
80+ }
81+ return 0;
82+ }
83+
84+ property real itemOpacity: {
85+ // First/last items are special
86+ if (index == 0 || index == priv.listView.count-1) {
87+ if (priv.distanceFromEdge < 0) {
88+ // Fade from 1 to 0 in the distance of 3 * foldingAreaHeight (which is when the next item reaches the edge)
89+ return 1.0 - (-priv.distanceFromEdge / (priv.listView.foldingAreaHeight * 3))
90+ }
91+ return 1; // Don't make first/last item transparent as long as inside the view
92+ }
93+
94+ // Are we already completely outside the flickable? Fade from 0.75 to 0 in 2 items height
95+ if (priv.distanceFromEdge < 0) {
96+ // proportion equation: -distanceFromEdge : 1-opacity = totalUnfoldedHeight : 0.75
97+ return 0.75 - (-priv.distanceFromEdge * 0.75 / (priv.totalUnfoldedHeight*2))
98+ }
99+
100+ // We are overlapping with the folding area, fade out to 0.75
101+ if (priv.overlapWithFoldingArea > 0) {
102+ // proportion equation: overlap : totalHeight = 1-opacity : 0.25
103+ return 1 - (priv.overlapWithFoldingArea * 0.25 / priv.listView.foldingAreaHeight)
104+ }
105+ return 1;
106+ }
107+
108+ property real brightness: {
109+ // First/last items are special
110+ if (index == 0 || index == priv.listView.count-1) {
111+ if (priv.distanceFromEdge < 0) {
112+ return -(-priv.distanceFromEdge / (priv.listView.foldingAreaHeight * 3))
113+ }
114+ return 0;
115+ }
116+ // Are we already completely outside the flickable? Fade from 0.7 to 0 in 2 items height
117+ if (priv.distanceFromEdge < 0) {
118+ return -0.3 - (-priv.distanceFromEdge * 0.1 / (priv.totalUnfoldedHeight*2))
119+ }
120+
121+ // We are overlapping with the folding area, fade out to 0.7
122+ if (priv.overlapWithFoldingArea > 0) {
123+ return - (priv.overlapWithFoldingArea * 0.3 / priv.listView.foldingAreaHeight)
124+ }
125+ return 0;
126+ }
127+
43128 Item {
44129 id: iconItem
45130 width: parent.width
@@ -58,22 +143,8 @@ Item {
58143 sourceSize.height: iconShape.height
59144 source: "../graphics/applicationIcons/" + root.iconName + ".png"
60145 }
61−
62− MouseArea {
63− id: mouseArea
64− anchors.fill: parent
65− onClicked: root.clicked()
66− onCanceled: root.released()
67− preventStealing: false
68−
69− onPressAndHold: {
70− root.state = "moving"
71− }
72− onReleased: {
73− root.state = "docked"
74− }
75− }
76146 }
147+
77148 BorderImage {
78149 id: overlayHighlight
79150 anchors.centerIn: iconItem
@@ -81,7 +152,7 @@ Item {
81152 source: isSelected ? "graphics/selected.sci" : "graphics/non-selected.sci"
82153 width: root.width + units.gu(0.5)
83154 height: width
84− property bool isSelected: root.highlighted || mouseArea.pressed
155+ property bool isSelected: root.highlighted
85156 onIsSelectedChanged: shaderEffectSource.scheduleUpdate();
86157 }
87158 }
@@ -93,16 +164,15 @@ Item {
93164 width: parent.width
94165 height: parent.height
95166 property real itemOpacity: root.itemOpacity
96− property real brightness: 0
167+ property real brightness: root.brightness
97168 property real angle: root.angle
98169 rotation: root.inverted ? 180 : 0
99170
100−
101171 property variant source: ShaderEffectSource {
102172 id: shaderEffectSource
103173 sourceItem: iconItem
104174 hideSource: true
105− live: false
175+ //live: false
106176 }
107177
108178 transform: [
@@ -167,123 +237,4 @@ Item {
167237 property real overlapWithFoldingArea: listView.foldingAreaHeight - distanceFromEdge
168238 }
169239
170− states: [
171− State {
172− name: "docked"
173− PropertyChanges {
174− target: root
175−
176− // This is the offset that keeps the items inside the panel
177− offset: {
178− // First/last items are special
179− if (index == 0 || index == priv.listView.count-1) {
180− // Just keep them bound to the edges in case they're outside of the visible area
181− if (priv.distanceFromEdge < 0) {
182− return (-priv.distanceFromEdge - (height - effectiveHeight)) * priv.orientationFlag;
183− }
184− return 0;
185− }
186−
187− // Are we already completely outside the flickable? Stop the icon here.
188− if (priv.distanceFromEdge < -priv.totalUnfoldedHeight) {
189− return (-priv.distanceFromEdge - (root.height - effectiveHeight)) * priv.orientationFlag;
190− }
191−
192− // We're touching the edge, move slower than the actual flicking speed.
193− if (priv.distanceFromEdge < 0) {
194− return (Math.abs(priv.distanceFromEdge) * priv.totalEffectiveHeight / priv.totalUnfoldedHeight) * priv.orientationFlag
195− }
196− return 0;
197− }
198−
199− // The angle used for rotating
200− angle: {
201− // First/last items are special
202− if (index == 0 || index == priv.listView.count-1) {
203− if (priv.distanceFromEdge < 0) {
204− // proportion equation: distanceFromTopEdge : angle = totalUnfoldedHeight/2 : maxAngle
205− return Math.max(-maxAngle, priv.distanceFromEdge * maxAngle / (priv.listView.foldingAreaHeight)) * priv.orientationFlag
206− }
207− return 0; // Don't fold first/last item as long as inside the view
208− }
209−
210− // Are we in the already completely outside the flickable? Fold for the last 5 degrees
211− if (priv.distanceFromEdge < 0) {
212− // proportion equation: -distanceFromTopEdge : angle = totalUnfoldedHeight : 5
213− return Math.max(-maxAngle, (priv.distanceFromEdge * 5 / priv.listView.foldingAreaHeight) - (maxAngle-5)) * priv.orientationFlag
214− }
215−
216− // We are overlapping with the folding area, fold the icon to maxAngle - 5 degrees
217− if (priv.overlapWithFoldingArea > 0) {
218− // proportion equation: overlap: totalHeight = angle : (maxAngle - 5)
219− return -priv.overlapWithFoldingArea * (maxAngle -5) / priv.listView.foldingAreaHeight * priv.orientationFlag;
220− }
221− return 0;
222− }
223−
224− itemOpacity: {
225− // First/last items are special
226− if (index == 0 || index == priv.listView.count-1) {
227− if (priv.distanceFromEdge < 0) {
228− // Fade from 1 to 0 in the distance of 3 * foldingAreaHeight (which is when the next item reaches the edge)
229− return 1.0 - (-priv.distanceFromEdge / (priv.listView.foldingAreaHeight * 3))
230− }
231− return 1; // Don't make first/last item transparent as long as inside the view
232− }
233−
234− // Are we already completely outside the flickable? Fade from 0.75 to 0 in 2 items height
235− if (priv.distanceFromEdge < 0) {
236− // proportion equation: -distanceFromEdge : 1-opacity = totalUnfoldedHeight : 0.75
237− return 0.75 - (-priv.distanceFromEdge * 0.75 / (priv.totalUnfoldedHeight*2))
238− }
239−
240− // We are overlapping with the folding area, fade out to 0.75
241− if (priv.overlapWithFoldingArea > 0) {
242− // proportion equation: overlap : totalHeight = 1-opacity : 0.25
243− return 1 - (priv.overlapWithFoldingArea * 0.25 / priv.listView.foldingAreaHeight)
244− }
245− return 1;
246− }
247−
248− brightness: {
249− // First/last items are special
250− if (index == 0 || index == priv.listView.count-1) {
251− if (priv.distanceFromEdge < 0) {
252− return -(-priv.distanceFromEdge / (priv.listView.foldingAreaHeight * 3))
253− }
254− return 0;
255− }
256− // Are we already completely outside the flickable? Fade from 0.7 to 0 in 2 items height
257− if (priv.distanceFromEdge < 0) {
258− return -0.3 - (-priv.distanceFromEdge * 0.1 / (priv.totalUnfoldedHeight*2))
259− }
260−
261− // We are overlapping with the folding area, fade out to 0.7
262− if (priv.overlapWithFoldingArea > 0) {
263− return - (priv.overlapWithFoldingArea * 0.3 / priv.listView.foldingAreaHeight)
264− }
265− return 0;
266− }
267− }
268− },
269−
270− State {
271− name: "moving"
272− PropertyChanges {
273− target: launcherDelegate;
274− offset: 0
275− angle: 0
276− }
277− PropertyChanges {
278− target: root
279− highlighted: true
280− dragging: true
281− }
282− PropertyChanges {
283− target: mouseArea
284− preventStealing: true
285− drag.target: root
286− }
287− }
288− ]
289240}
Launcher/LauncherPanel.qml+175−6
@@ -16,6 +16,8 @@
1616
1717import QtQuick 2.0
1818import Ubuntu.Components 0.1
19+import Ubuntu.Components.Popups 0.1
20+import Ubuntu.Components.ListItems 0.1 as ListItems
1921import Unity 0.1
2022import Unity.Launcher 0.1
2123import "../Components/ListItems"
@@ -26,9 +28,9 @@ Item {
2628 rotation: inverted ? 180 : 0
2729
2830 property var model
29− property bool inverted: true
31+ property bool inverted: false
3032 property bool dragging: false
31− property bool moving: launcherListView.moving || launcherListView.flicking
33+ property bool moving: launcherListView.moving || launcherListView.flicking || dndArea.draggedItem !== undefined
3234 property int dragPosition: 0
3335 property int highlightIndex: -1
3436
@@ -110,7 +112,7 @@ Item {
110112 height: parent.height - dashItem.height - parent.spacing*2
111113 model: root.model
112114 cacheBuffer: itemSize * 3
113− snapMode: ListView.SnapToItem
115+ snapMode: interactive ? ListView.SnapToItem : ListView.NoSnap
114116 highlightRangeMode: ListView.ApplyRange
115117 preferredHighlightBegin: (height - itemSize) / 2
116118 preferredHighlightEnd: (height + itemSize) / 2
@@ -120,17 +122,25 @@ Item {
120122 property int itemSize: width
121123 property int clickFlickSpeed: units.gu(60)
122124
125+ displaced: Transition {
126+ NumberAnimation { properties: "x,y"; duration: 100 }
127+ }
128+
123129 delegate: LauncherDelegate {
124130 id: launcherDelegate
125131 objectName: "launcherDelegate" + index
126132 width: launcherListView.itemSize
127− height: launcherListView.itemSize
133+ height: dragging ? units.gu(2) : launcherListView.itemSize
128134 iconName: model.icon
129135 inverted: root.inverted
130− highlighted: root.dragging && index === root.highlightIndex
136+ highlighted: dragging && index === root.highlightIndex
131137 z: -Math.abs(offset)
132− state: "docked"
133138 maxAngle: 60
139+ property bool dragging: false
140+
141+ Behavior on height {
142+ NumberAnimation { duration: 200 }
143+ }
134144
135145 onClicked: {
136146 // First/last item do the scrolling at more than 12 degrees
@@ -156,6 +166,145 @@ Item {
156166 }
157167 }
158168
169+ MouseArea {
170+ id: dndArea
171+ anchors.fill: parent
172+ anchors.topMargin: launcherListView.topMargin
173+ anchors.bottomMargin: launcherListView.bottomMargin
174+
175+ property int draggedIndex: -1
176+ property var selectedItem
177+ property int startX
178+ property int startY
179+
180+
181+ Item {
182+ id: fakeDragItem
183+ width: launcherListView.itemSize
184+ height: launcherListView.itemSize
185+ visible: dndArea.draggedIndex >= 0
186+ property string iconName
187+
188+ UbuntuShape {
189+ id: iconShape
190+ anchors.fill: parent
191+ anchors.margins: units.gu(0.5)
192+ radius: "medium"
193+
194+ image: Image {
195+ id: iconImage
196+ sourceSize.width: iconShape.width
197+ sourceSize.height: iconShape.height
198+ source: "../graphics/applicationIcons/" + fakeDragItem.iconName + ".png"
199+ }
200+ }
201+ BorderImage {
202+ id: overlayHighlight
203+ anchors.centerIn: fakeDragItem
204+ rotation: inverted ? 180 : 0
205+ source: isSelected ? "graphics/selected.sci" : "graphics/non-selected.sci"
206+ width: fakeDragItem.width + units.gu(0.5)
207+ height: width
208+ }
209+ }
210+
211+/* LauncherDelegate {
212+ id: fakeDragItem
213+ visible: dndArea.draggedItem !== undefined
214+ iconName: "gmail"
215+ height: launcherListView.itemSize
216+ width: height
217+ rotation: root.rotation
218+ }
219+*/
220+ onPressed: {
221+ var realContentY = launcherListView.contentY + launcherListView.topMargin
222+ selectedItem = launcherListView.itemAt(mouseX, mouseY + realContentY)
223+ selectedItem.highlighted = true
224+
225+ }
226+
227+ onClicked: {
228+ }
229+
230+ onCanceled: {
231+ selectedItem.highlighted = false
232+ }
233+
234+ onReleased: {
235+ print("released");
236+ selectedItem.highlighted = false
237+ selectedItem.opacity = 1
238+ selectedItem.dragging = false;
239+ selectedItem = undefined;
240+
241+ draggedIndex = -1;
242+ drag.target = undefined
243+ launcherListView.interactive = true;
244+ }
245+
246+ onPressAndHold: {
247+ var realItemSize = launcherListView.itemSize + launcherListView.spacing
248+ var realContentY = launcherListView.contentY + launcherListView.topMargin
249+ draggedIndex = Math.floor((mouseY + realContentY) / realItemSize);
250+
251+ launcherListView.interactive = false
252+
253+ var yOffset = draggedIndex > 0 ? (mouseY + realContentY) % (draggedIndex * realItemSize) : mouseY + realContentY
254+
255+ fakeDragItem.iconName = launcherListView.model.get(draggedIndex).icon
256+ fakeDragItem.x = 0
257+ fakeDragItem.y = mouseY - yOffset
258+ drag.target = fakeDragItem
259+
260+ selectedItem.opacity = 0
261+
262+ startX = mouseX
263+ startY = mouseY
264+ }
265+
266+ onPositionChanged: {
267+ if (draggedIndex >= 0) {
268+
269+ if (!selectedItem.dragging) {
270+ var distance = Math.max(Math.abs(mouseX - startX), Math.abs(mouseY - startY))
271+ if (distance > launcherListView.itemSize) {
272+ print("starting drag")
273+ selectedItem.dragging = true
274+ }
275+ }
276+ if (!selectedItem.dragging) {
277+ return
278+ }
279+
280+ //launcherPanel.dragPosition = inverted ? launcherListView.height - y : y
281+ //launcherPanel.dragPosition = mouseY
282+
283+ var realContentY = launcherListView.contentY + launcherListView.topMargin
284+ var realItemSize = launcherListView.itemSize + launcherListView.spacing
285+ var itemCenterY = fakeDragItem.y + fakeDragItem.height / 2
286+
287+ // Move it down by the the missing size to compensate index calculation with only expanded items
288+ itemCenterY += selectedItem.height / 2
289+
290+ var newIndex = (itemCenterY + realContentY) / realItemSize
291+
292+ if (newIndex > draggedIndex + 1) {
293+ newIndex = draggedIndex + 1
294+ } else if (newIndex < draggedIndex) {
295+ newIndex = draggedIndex -1
296+ } else {
297+ return
298+ }
299+
300+ if (newIndex >= 0 && newIndex < launcherListView.count) {
301+ print("moving", draggedIndex, "to", newIndex)
302+ launcherListView.model.move(draggedIndex, newIndex)
303+ draggedIndex = newIndex
304+ }
305+ }
306+ }
307+ }
159308 MouseArea {
160309 id: topFoldingArea
161310 anchors {
@@ -184,4 +333,24 @@ Item {
184333 }
185334 }
186335 }
336+
337+ Component {
338+ id: popoverComponent
339+ Popover {
340+ id: popover
341+ width: units.gu(20)
342+
343+ Column {
344+ width: parent.width
345+ height: childrenRect.height
346+ Repeater {
347+ model: 2
348+ ListItems.Standard {
349+ text: "foo bar baz"
350+ }
351+ }
352+ }
353+ }
354+ }
355+
187356}
plugins/Unity/Launcher/launchermodel.cpp+19−2
@@ -82,8 +82,25 @@ unity::shell::launcher::LauncherItemInterface *LauncherModel::get(int index) con
8282
8383void LauncherModel::move(int oldIndex, int newIndex)
8484{
85− // Perform the move in our list
86− beginMoveRows(QModelIndex(), oldIndex, oldIndex, QModelIndex(), newIndex);
85+ // Make sure its not moved outside the lists
86+ if (newIndex < 0) {
87+ newIndex = 0;
88+ }
89+ if (newIndex >= m_list.count()) {
90+ newIndex = m_list.count()-1;
91+ }
92+
93+ // Nothing to do?
94+ if (oldIndex == newIndex) {
95+ return;
96+ }
97+
98+ // QList's and QAbstractItemModel's move implementation differ when moving an item up the list :/
99+ // While QList needs the index in the resulting list, beginMoveRows expects it to be in the current list
100+ // adjust the model's index by +1 in case we're moving upwards
101+ int newModelIndex = newIndex > oldIndex ? newIndex+1 : newIndex;
102+
103+ beginMoveRows(QModelIndex(), oldIndex, oldIndex, QModelIndex(), newModelIndex);
87104 m_list.move(oldIndex, newIndex);
88105 endMoveRows();
89106