@@ -16,8 +16,10 @@
17import QtQuick 2.0
17import QtQuick 2.0
18import Ubuntu.Components 0.1
18import Ubuntu.Components 0.1
19+import Ubuntu.Components.ListItems 0.1 as ListItems
19import Unity 0.1
20import Unity 0.1
20import Unity.Launcher 0.1
21import Unity.Launcher 0.1
22+import Ubuntu.Components.Popups 0.1
21import "../Components/ListItems"
23import "../Components/ListItems"
@@ -29,53 +31,33 @@ Item {
29 property bool inverted: true
31 property bool inverted: true
30 property bool dragging: false
32 property bool dragging: false
31 property bool moving: launcherListView.moving || launcherListView.flicking
33 property bool moving: launcherListView.moving || launcherListView.flicking
32− property int dragPosition: 0
34+ property bool preventHiding: moving || dndArea.draggedIndex >= 0 || dndArea.quickListPopover !== null || dndArea.pressed
33 property int highlightIndex: -1
35 property int highlightIndex: -1
35 signal applicationSelected(string desktopFile)
37 signal applicationSelected(string desktopFile)
36 signal dashItemSelected(int index)
38 signal dashItemSelected(int index)
38− onDragPositionChanged: {
39− var effectiveDragPosition = root.inverted ? launcherListView.height - dragPosition : dragPosition - mainColumn.anchors.margins
41− var hiddenContentHeight = launcherListView.contentHeight - launcherListView.height
42− // Shortening scrollable height because the first/last item needs to be fully expanded before reaching the top/bottom
43− var scrollableHeight = launcherListView.height - (launcherListView.itemSize + launcherColumn.spacing) *2
44− // As we shortened the scrollableHeight, lets move everything down by the itemSize
45− var shortenedEffectiveDragPosition = effectiveDragPosition - launcherListView.itemSize - launcherColumn.spacing
46− var newContentY = shortenedEffectiveDragPosition * hiddenContentHeight / scrollableHeight
48− // limit top/bottom to prevent overshooting
49− launcherListView.contentY = Math.min(hiddenContentHeight, Math.max(0, newContentY));
51− // Now calculate the current index:
52− // > the current mouse position + the hidden/scolled content on top is the mouse position in the averall view
53− // > adjust that removing all the margins
54− // > divide by itemSize to get index
55− highlightIndex = (effectiveDragPosition + launcherListView.contentY - mainColumn.anchors.margins*3 - launcherColumn.spacing/2) / (launcherListView.itemSize + launcherColumn.spacing)
58 BorderImage {
40 BorderImage {
59 id: background
41 id: background
60 source: "graphics/launcher_bg.sci"
42 source: "graphics/launcher_bg.sci"
61 anchors.fill: parent
43 anchors.fill: parent
44+ anchors.rightMargin: root.inverted ? 0 : -units.gu(1)
45+ anchors.leftMargin: root.inverted ? -units.gu(1) : 0
46+ rotation: root.rotation
65 id: mainColumn
50 id: mainColumn
67 fill: parent
52 fill: parent
68− topMargin: units.gu(0.5)
69− bottomMargin: units.gu(1)
70 leftMargin: units.gu(0.5)
53 leftMargin: units.gu(0.5)
71 rightMargin: units.gu(0.5)
54 rightMargin: units.gu(0.5)
73− spacing: units.gu(0.5)
75 MouseArea {
57 MouseArea {
76 id: dashItem
58 id: dashItem
77 width: parent.width
59 width: parent.width
78− height: units.gu(6.5)
60+ height: units.gu(7)
79 onClicked: root.dashItemSelected(0)
61 onClicked: root.dashItemSelected(0)
@@ -100,87 +82,392 @@ Item {
100 anchors.left: parent.left
82 anchors.left: parent.left
101 anchors.right: parent.right
83 anchors.right: parent.right
102 height: parent.height - dashItem.height - parent.spacing*2
84 height: parent.height - dashItem.height - parent.spacing*2
104− id: launcherListView
86+ Item {
105− objectName: "launcherListView"
106 anchors.fill: parent
87 anchors.fill: parent
92+ objectName: "launcherListView"
107− anchors.topMargin: -itemSize
93+ anchors {
95+ topMargin: -extensionSize + units.gu(0.5)
108− anchors.bottomMargin: -itemSize
96+ bottomMargin: -extensionSize + units.gu(1)
109− topMargin: itemSize
98+ topMargin: extensionSize
110− bottomMargin: itemSize
99+ bottomMargin: extensionSize
111− height: parent.height - dashItem.height - parent.spacing*2
100+ height: parent.height - dashItem.height - parent.spacing*2
112− model: root.model
101+ model: root.model
113− cacheBuffer: itemSize * 3
102+ cacheBuffer: itemHeight * 3
114− snapMode: ListView.SnapToItem
103+ snapMode: interactive ? ListView.SnapToItem : ListView.NoSnap
115− highlightRangeMode: ListView.ApplyRange
104+ highlightRangeMode: ListView.ApplyRange
116− preferredHighlightBegin: (height - itemSize) / 2
105+ preferredHighlightBegin: (height - itemHeight) / 2
117− preferredHighlightEnd: (height + itemSize) / 2
106+ preferredHighlightEnd: (height + itemHeight) / 2
118−
107+ spacing: units.gu(0.5)
109+ // The size of the area the ListView is extended to make sure items are not
110+ // destroyed when dragging them outside the list. This needs to be at least
111+ // itemHeight to prevent folded items from disappearing and DragArea limits
112+ // need to be smaller than this size to avoid breakage.
113+ property int extensionSize: itemHeight * 3
119− // The height of the area where icons start getting folded
115+ // The height of the area where icons start getting folded
120− property int foldingAreaHeight: itemSize * 0.75
116+ property int foldingAreaHeight: itemHeight * 0.75
121− property int itemSize: width
117+ property int itemWidth: width
118+ property int itemHeight: width * 7.5 / 8
122− property int clickFlickSpeed: units.gu(60)
119+ property int clickFlickSpeed: units.gu(60)
123−
120+ property int draggedIndex: dndArea.draggedIndex
124− delegate: LauncherDelegate {
121+ property real realContentY: contentY - originY + topMargin
122+ property int realItemHeight: itemHeight + spacing
124+ displaced: Transition {
125+ NumberAnimation { properties: "x,y"; duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing }
128+ delegate: FoldingLauncherDelegate {
125− id: launcherDelegate
129+ id: launcherDelegate
126− objectName: "launcherDelegate" + index
130+ objectName: "launcherDelegate" + index
127− width: launcherListView.itemSize
131+ itemHeight: launcherListView.itemHeight
128− height: launcherListView.itemSize
132+ itemWidth: launcherListView.itemWidth
129− iconName: model.icon
135+ iconName: model.icon
137+ progress: model.progress
130− inverted: root.inverted
138+ inverted: root.inverted
131− highlighted: root.dragging && index === root.highlightIndex
139+ highlighted: dragging && index === root.highlightIndex
132− z: -Math.abs(offset)
140+ z: -Math.abs(offset)
134− maxAngle: 60
141+ maxAngle: 60
135−
142+ property bool dragging: false
146+ objectName: "dropIndicator"
147+ anchors.centerIn: parent
148+ width: parent.width + mainColumn.anchors.leftMargin + mainColumn.anchors.rightMargin
155+ when: dndArea.selectedItem === launcherDelegate && fakeDragItem.visible && !dragging
157+ target: launcherDelegate
165+ target: launcherDelegate
170+ target: dropIndicator
176+ when: dndArea.draggedIndex >= 0 && (dndArea.preDragging || dndArea.dragging || dndArea.postDragging) && dndArea.draggedIndex != index
178+ target: launcherDelegate
190+ NumberAnimation { properties: "itemOpacity"; duration: UbuntuAnimation.FastDuration }
195+ NumberAnimation { properties: "itemOpacity"; duration: UbuntuAnimation.FastDuration }
196+ UbuntuNumberAnimation { properties: "angle,offset" }
201+ NumberAnimation { properties: "itemOpacity"; duration: UbuntuAnimation.BriskDuration }
202+ UbuntuNumberAnimation { properties: "angle,offset" }
207+ UbuntuNumberAnimation { properties: "height" }
208+ NumberAnimation { target: dropIndicator; properties: "opacity"; duration: UbuntuAnimation.FastDuration }
213+ NumberAnimation { target: dropIndicator; properties: "opacity"; duration: UbuntuAnimation.FastDuration }
214+ NumberAnimation { properties: "itemOpacity"; duration: UbuntuAnimation.BriskDuration }
215+ SequentialAnimation {
216+ UbuntuNumberAnimation { properties: "height" }
217+ PropertyAction { target: dndArea; property: "postDragging"; value: false }
218+ PropertyAction { target: dndArea; property: "draggedIndex"; value: -1 }
226+ objectName: "dndArea"
229+ topMargin: launcherListView.topMargin
230+ bottomMargin: launcherListView.bottomMargin
232+ drag.minimumY: -launcherListView.topMargin
233+ drag.maximumY: height + launcherListView.bottomMargin
235+ property int draggedIndex: -1
236+ property var selectedItem
237+ property bool preDragging: false
238+ property bool dragging: selectedItem !== undefined && selectedItem.dragging
239+ property bool postDragging: false
242+ property var quickListPopover: null
245+ selectedItem = launcherListView.itemAt(mouseX, mouseY + launcherListView.realContentY)
246+ selectedItem.highlighted = true
136− onClicked: {
249+ onClicked: {
250+ var index = Math.floor((mouseY + launcherListView.realContentY) / launcherListView.realItemHeight);
251+ var clickedItem = launcherListView.itemAt(mouseX, mouseY + launcherListView.realContentY)
137− // First/last item do the scrolling at more than 12 degrees
253+ // First/last item do the scrolling at more than 12 degrees
138− if (index == 0 || index == launcherListView.count -1) {
254+ if (index == 0 || index == launcherListView.count - 1) {
255+ if (clickedItem.angle > 12) {
256+ launcherListView.flick(0, -launcherListView.clickFlickSpeed);
257+ } else if (clickedItem.angle < -12) {
258+ launcherListView.flick(0, launcherListView.clickFlickSpeed);
260+ root.applicationSelected(LauncherModel.get(index).desktopFile);
265+ // the rest launches apps up to an angle of 30 degrees
139− if (angle > 12) {
266+ if (clickedItem.angle > 30) {
140 launcherListView.flick(0, -launcherListView.clickFlickSpeed);
267 launcherListView.flick(0, -launcherListView.clickFlickSpeed);
141− } else if (angle < -12) {
268+ } else if (clickedItem.angle < -30) {
142 launcherListView.flick(0, launcherListView.clickFlickSpeed);
269 launcherListView.flick(0, launcherListView.clickFlickSpeed);
144 root.applicationSelected(LauncherModel.get(index).desktopFile);
271 root.applicationSelected(LauncherModel.get(index).desktopFile);
149− // the rest launches apps up to an angle of 30 degrees
275+ onCanceled: {
150− if (angle > 30) {
276+ selectedItem.highlighted = false
151− launcherListView.flick(0, -launcherListView.clickFlickSpeed);
277+ selectedItem = undefined;
152− } else if (angle < -30) {
278+ preDragging = false;
153− launcherListView.flick(0, launcherListView.clickFlickSpeed);
279+ postDragging = false;
155− root.applicationSelected(LauncherModel.get(index).desktopFile);
281+
283+ var droppedIndex = draggedIndex;
285+ postDragging = true;
290+ selectedItem.highlighted = false
291+ selectedItem.dragging = false;
292+ selectedItem = undefined;
293+ preDragging = false;
295+ drag.target = undefined
297+ progressiveScrollingTimer.stop();
298+ launcherListView.interactive = true;
299+ if (droppedIndex >= launcherListView.count - 2 && postDragging) {
300+ launcherListView.flick(0, -launcherListView.clickFlickSpeed);
305+ if (Math.abs(selectedItem.angle) > 30) {
309+ draggedIndex = Math.floor((mouseY + launcherListView.realContentY) / launcherListView.realItemHeight);
311+ // Opening QuickList
312+ var quickListModel = launcherListView.model.get(draggedIndex).quickList
313+ var quickListAppId = launcherListView.model.get(draggedIndex).appId
315+ quickListPopover = PopupUtils.open(popoverComponent, selectedItem,
316+ {model: quickListModel, appId: quickListAppId})
318+ launcherListView.interactive = false
320+ var yOffset = draggedIndex > 0 ? (mouseY + launcherListView.realContentY) % (draggedIndex * launcherListView.realItemHeight) : mouseY + launcherListView.realContentY
322+ fakeDragItem.iconName = launcherListView.model.get(draggedIndex).icon
324+ fakeDragItem.y = mouseY - yOffset + launcherListView.anchors.topMargin + launcherListView.topMargin
325+ fakeDragItem.angle = selectedItem.angle * (root.inverted ? -1 : 1)
326+ fakeDragItem.offset = selectedItem.offset * (root.inverted ? -1 : 1)
327+ fakeDragItem.count = LauncherModel.get(draggedIndex).count
328+ fakeDragItem.progress = LauncherModel.get(draggedIndex).progress
329+ fakeDragItem.flatten()
330+ drag.target = fakeDragItem
336+ onPositionChanged: {
337+ if (draggedIndex >= 0) {
338+ if (!selectedItem.dragging) {
339+ var distance = Math.max(Math.abs(mouseX - startX), Math.abs(mouseY - startY))
340+ if (!preDragging && distance > units.gu(1.5)) {
342+ PopupUtils.close(quickListPopover)
344+ if (distance > launcherListView.itemHeight) {
345+ selectedItem.dragging = true
346+ preDragging = false;
349+ if (!selectedItem.dragging) {
353+ var itemCenterY = fakeDragItem.y + fakeDragItem.height / 2
355+ // Move it down by the the missing size to compensate index calculation with only expanded items
356+ itemCenterY += (launcherListView.itemHeight - selectedItem.height) / 2
358+ if (mouseY > launcherListView.height - launcherListView.topMargin - launcherListView.bottomMargin - launcherListView.realItemHeight) {
359+ progressiveScrollingTimer.downwards = false
360+ progressiveScrollingTimer.start()
361+ } else if (mouseY < launcherListView.realItemHeight) {
362+ progressiveScrollingTimer.downwards = true
363+ progressiveScrollingTimer.start()
365+ progressiveScrollingTimer.stop()
368+ var newIndex = (itemCenterY + launcherListView.realContentY) / launcherListView.realItemHeight
370+ if (newIndex > draggedIndex + 1) {
371+ newIndex = draggedIndex + 1
372+ } else if (newIndex < draggedIndex) {
373+ newIndex = draggedIndex -1
378+ if (newIndex >= 0 && newIndex < launcherListView.count) {
379+ launcherListView.model.move(draggedIndex, newIndex)
380+ draggedIndex = newIndex
386+ id: progressiveScrollingTimer
390+ property bool downwards: true
393+ var minY = -launcherListView.topMargin
394+ if (launcherListView.contentY > minY) {
395+ launcherListView.contentY = Math.max(launcherListView.contentY - units.dp(2), minY)
398+ var maxY = launcherListView.contentHeight - launcherListView.height + launcherListView.topMargin + launcherListView.originY
399+ if (launcherListView.contentY < maxY) {
400+ launcherListView.contentY = Math.min(launcherListView.contentY + units.dp(2), maxY)
160− MouseArea {
408+ LauncherDelegate {
161− id: topFoldingArea
409+ id: fakeDragItem
162− anchors {
410+ objectName: "fakeDragItem"
163− left: parent.left
411+ visible: dndArea.draggedIndex >= 0 && !dndArea.postDragging
164− right: parent.right
412+ itemWidth: launcherListView.itemWidth
166− topMargin: launcherListView.topMargin
413+ itemHeight: launcherListView.itemHeight
167− }
414+ height: itemHeight
168− height: launcherListView.itemSize / 2
415+ width: itemWidth
169− enabled: launcherListView.contentY > -launcherListView.topMargin
416+ rotation: root.rotation
170− onClicked: launcherListView.flick(0, launcherListView.clickFlickSpeed)
417+ highlighted: true
420+ function flatten() {
421+ fakeDragItemAnimation.start();
173− MouseArea {
424+ UbuntuNumberAnimation {
174− id: bottomFoldingArea
425+ id: fakeDragItemAnimation
175− anchors {
426+ target: fakeDragItem;
176− left: parent.left
427+ properties: "angle,offset";
177− right: parent.right
428+ to: 0
178− bottom: parent.bottom
429+ }
179− bottomMargin: launcherListView.bottomMargin
430+ }
435+ id: popoverComponent
440+ property string appId
441+ contentWidth: quickListColumn.width
443+ // FIXME: There's a bug in the Popover positioning that it covers the item in case it is rotated.
444+ // https://bugs.launchpad.net/ubuntu-ui-toolkit/+bug/1204470
445+ // For now, let's move the Popover around with callerMargin.
446+ // Remove popupMargin once the bug is fixed.
447+ property int popupMargin: root.inverted ? launcherListView.itemHeight : 0;
449+ callerMargin: units.gu(1) + popupMargin
454+ height: childrenRect.height
457+ model: popover.model
459+ ListItems.Standard {
460+ objectName: "quickListEntry" + index
461+ text: (model.clickable ? "" : "<b>") + model.label + (model.clickable ? "" : "</b>")
462+ highlightWhenPressed: model.clickable
464+ if (!model.clickable) {
467+ LauncherModel.quickListActionInvoked(appId, index);
468+ PopupUtils.close(popover);
181− height: launcherListView.itemSize / 2
182− enabled: launcherListView.contentHeight - launcherListView.height - launcherListView.contentY > -launcherListView.bottomMargin
183− onClicked: launcherListView.flick(0, -launcherListView.clickFlickSpeed)