@@ -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,429 @@ 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+ // In case the start dragging transition is running, we need to delay the
125+ // move because the displaced transition would clash with it and cause items
126+ // to be moved to wrong places
127+ property bool draggingTransitionRunning: false
128+ property int scheduledMoveTo: -1
130+ displaced: Transition {
131+ NumberAnimation { properties: "x,y"; duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing }
134+ delegate: FoldingLauncherDelegate {
125− id: launcherDelegate
135+ id: launcherDelegate
126− objectName: "launcherDelegate" + index
136+ objectName: "launcherDelegate" + index
127− width: launcherListView.itemSize
137+ itemHeight: launcherListView.itemHeight
128− height: launcherListView.itemSize
138+ itemWidth: launcherListView.itemWidth
129− iconName: model.icon
141+ iconName: model.icon
143+ progress: model.progress
130− inverted: root.inverted
144+ inverted: root.inverted
131− highlighted: root.dragging && index === root.highlightIndex
145+ highlighted: dragging && index === root.highlightIndex
132− z: -Math.abs(offset)
146+ z: -Math.abs(offset)
134− maxAngle: 60
147+ maxAngle: 60
135−
148+ property bool dragging: false
152+ objectName: "dropIndicator"
153+ anchors.centerIn: parent
154+ width: parent.width + mainColumn.anchors.leftMargin + mainColumn.anchors.rightMargin
161+ when: dndArea.selectedItem === launcherDelegate && fakeDragItem.visible && !dragging
163+ target: launcherDelegate
171+ target: launcherDelegate
176+ target: dropIndicator
182+ when: dndArea.draggedIndex >= 0 && (dndArea.preDragging || dndArea.dragging || dndArea.postDragging) && dndArea.draggedIndex != index
184+ target: launcherDelegate
196+ NumberAnimation { properties: "itemOpacity"; duration: UbuntuAnimation.FastDuration }
201+ NumberAnimation { properties: "itemOpacity"; duration: UbuntuAnimation.FastDuration }
202+ UbuntuNumberAnimation { properties: "angle,offset" }
207+ NumberAnimation { properties: "itemOpacity"; duration: UbuntuAnimation.BriskDuration }
208+ UbuntuNumberAnimation { properties: "angle,offset" }
211+ id: draggingTransition
214+ SequentialAnimation {
215+ PropertyAction { target: launcherListView; property: "draggingTransitionRunning"; value: true }
217+ UbuntuNumberAnimation { properties: "height" }
218+ NumberAnimation { target: dropIndicator; properties: "opacity"; duration: UbuntuAnimation.FastDuration }
222+ if (launcherListView.scheduledMoveTo > -1) {
223+ launcherListView.model.move(dndArea.draggedIndex, launcherListView.scheduledMoveTo)
224+ dndArea.draggedIndex = launcherListView.scheduledMoveTo
225+ launcherListView.scheduledMoveTo = -1
229+ PropertyAction { target: launcherListView; property: "draggingTransitionRunning"; value: false }
235+ NumberAnimation { target: dropIndicator; properties: "opacity"; duration: UbuntuAnimation.FastDuration }
236+ NumberAnimation { properties: "itemOpacity"; duration: UbuntuAnimation.BriskDuration }
237+ SequentialAnimation {
238+ UbuntuNumberAnimation { properties: "height" }
239+ PropertyAction { target: dndArea; property: "postDragging"; value: false }
240+ PropertyAction { target: dndArea; property: "draggedIndex"; value: -1 }
248+ objectName: "dndArea"
251+ topMargin: launcherListView.topMargin
252+ bottomMargin: launcherListView.bottomMargin
254+ drag.minimumY: -launcherListView.topMargin
255+ drag.maximumY: height + launcherListView.bottomMargin
257+ property int draggedIndex: -1
258+ property var selectedItem
259+ property bool preDragging: false
260+ property bool dragging: selectedItem !== undefined && selectedItem !== null && selectedItem.dragging
261+ property bool postDragging: false
264+ property var quickListPopover: null
267+ selectedItem = launcherListView.itemAt(mouseX, mouseY + launcherListView.realContentY)
268+ if (selectedItem !== null) {
269+ selectedItem.highlighted = true
136− onClicked: {
273+ onClicked: {
274+ var index = Math.floor((mouseY + launcherListView.realContentY) / launcherListView.realItemHeight);
275+ var clickedItem = launcherListView.itemAt(mouseX, mouseY + launcherListView.realContentY)
277+ // Check if we actually clicked an item or only at the spacing in between
278+ if (clickedItem === null) {
137− // First/last item do the scrolling at more than 12 degrees
282+ // First/last item do the scrolling at more than 12 degrees
138− if (index == 0 || index == launcherListView.count -1) {
283+ if (index == 0 || index == launcherListView.count - 1) {
284+ if (clickedItem.angle > 12) {
285+ launcherListView.flick(0, -launcherListView.clickFlickSpeed);
286+ } else if (clickedItem.angle < -12) {
287+ launcherListView.flick(0, launcherListView.clickFlickSpeed);
289+ root.applicationSelected(LauncherModel.get(index).desktopFile);
294+ // the rest launches apps up to an angle of 30 degrees
139− if (angle > 12) {
295+ if (clickedItem.angle > 30) {
140 launcherListView.flick(0, -launcherListView.clickFlickSpeed);
296 launcherListView.flick(0, -launcherListView.clickFlickSpeed);
141− } else if (angle < -12) {
297+ } else if (clickedItem.angle < -30) {
142 launcherListView.flick(0, launcherListView.clickFlickSpeed);
298 launcherListView.flick(0, launcherListView.clickFlickSpeed);
144 root.applicationSelected(LauncherModel.get(index).desktopFile);
300 root.applicationSelected(LauncherModel.get(index).desktopFile);
149− // the rest launches apps up to an angle of 30 degrees
304+ onCanceled: {
150− if (angle > 30) {
305+ selectedItem.highlighted = false
151− launcherListView.flick(0, -launcherListView.clickFlickSpeed);
306+ selectedItem = undefined;
152− } else if (angle < -30) {
307+ preDragging = false;
153− launcherListView.flick(0, launcherListView.clickFlickSpeed);
308+ postDragging = false;
155− root.applicationSelected(LauncherModel.get(index).desktopFile);
310+
312+ var droppedIndex = draggedIndex;
314+ postDragging = true;
319+ if (!selectedItem) {
323+ selectedItem.highlighted = false
324+ selectedItem.dragging = false;
325+ selectedItem = undefined;
326+ preDragging = false;
328+ drag.target = undefined
330+ progressiveScrollingTimer.stop();
331+ launcherListView.interactive = true;
332+ if (droppedIndex >= launcherListView.count - 2 && postDragging) {
333+ launcherListView.flick(0, -launcherListView.clickFlickSpeed);
338+ if (Math.abs(selectedItem.angle) > 30) {
342+ draggedIndex = Math.floor((mouseY + launcherListView.realContentY) / launcherListView.realItemHeight);
344+ // Opening QuickList
345+ var quickListModel = launcherListView.model.get(draggedIndex).quickList
346+ var quickListAppId = launcherListView.model.get(draggedIndex).appId
348+ quickListPopover = PopupUtils.open(popoverComponent, selectedItem,
349+ {model: quickListModel, appId: quickListAppId})
351+ launcherListView.interactive = false
353+ var yOffset = draggedIndex > 0 ? (mouseY + launcherListView.realContentY) % (draggedIndex * launcherListView.realItemHeight) : mouseY + launcherListView.realContentY
355+ fakeDragItem.iconName = launcherListView.model.get(draggedIndex).icon
357+ fakeDragItem.y = mouseY - yOffset + launcherListView.anchors.topMargin + launcherListView.topMargin
358+ fakeDragItem.angle = selectedItem.angle * (root.inverted ? -1 : 1)
359+ fakeDragItem.offset = selectedItem.offset * (root.inverted ? -1 : 1)
360+ fakeDragItem.count = LauncherModel.get(draggedIndex).count
361+ fakeDragItem.progress = LauncherModel.get(draggedIndex).progress
362+ fakeDragItem.flatten()
363+ drag.target = fakeDragItem
369+ onPositionChanged: {
370+ if (draggedIndex >= 0) {
371+ if (!selectedItem.dragging) {
372+ var distance = Math.max(Math.abs(mouseX - startX), Math.abs(mouseY - startY))
373+ if (!preDragging && distance > units.gu(1.5)) {
375+ PopupUtils.close(quickListPopover)
377+ if (distance > launcherListView.itemHeight) {
378+ selectedItem.dragging = true
379+ preDragging = false;
382+ if (!selectedItem.dragging) {
386+ var itemCenterY = fakeDragItem.y + fakeDragItem.height / 2
388+ // Move it down by the the missing size to compensate index calculation with only expanded items
389+ itemCenterY += (launcherListView.itemHeight - selectedItem.height) / 2
391+ if (mouseY > launcherListView.height - launcherListView.topMargin - launcherListView.bottomMargin - launcherListView.realItemHeight) {
392+ progressiveScrollingTimer.downwards = false
393+ progressiveScrollingTimer.start()
394+ } else if (mouseY < launcherListView.realItemHeight) {
395+ progressiveScrollingTimer.downwards = true
396+ progressiveScrollingTimer.start()
398+ progressiveScrollingTimer.stop()
401+ var newIndex = (itemCenterY + launcherListView.realContentY) / launcherListView.realItemHeight
403+ if (newIndex > draggedIndex + 1) {
404+ newIndex = draggedIndex + 1
405+ } else if (newIndex < draggedIndex) {
406+ newIndex = draggedIndex -1
411+ if (newIndex >= 0 && newIndex < launcherListView.count) {
412+ if (launcherListView.draggingTransitionRunning) {
413+ launcherListView.scheduledMoveTo = newIndex
415+ launcherListView.model.move(draggedIndex, newIndex)
416+ draggedIndex = newIndex
423+ id: progressiveScrollingTimer
427+ property bool downwards: true
430+ var minY = -launcherListView.topMargin
431+ if (launcherListView.contentY > minY) {
432+ launcherListView.contentY = Math.max(launcherListView.contentY - units.dp(2), minY)
435+ var maxY = launcherListView.contentHeight - launcherListView.height + launcherListView.topMargin + launcherListView.originY
436+ if (launcherListView.contentY < maxY) {
437+ launcherListView.contentY = Math.min(launcherListView.contentY + units.dp(2), maxY)
160− MouseArea {
445+ LauncherDelegate {
161− id: topFoldingArea
446+ id: fakeDragItem
162− anchors {
447+ objectName: "fakeDragItem"
163− left: parent.left
448+ visible: dndArea.draggedIndex >= 0 && !dndArea.postDragging
164− right: parent.right
449+ itemWidth: launcherListView.itemWidth
166− topMargin: launcherListView.topMargin
450+ itemHeight: launcherListView.itemHeight
167− }
451+ height: itemHeight
168− height: launcherListView.itemSize / 2
452+ width: itemWidth
169− enabled: launcherListView.contentY > -launcherListView.topMargin
453+ rotation: root.rotation
170− onClicked: launcherListView.flick(0, launcherListView.clickFlickSpeed)
454+ highlighted: true
457+ function flatten() {
458+ fakeDragItemAnimation.start();
173− MouseArea {
461+ UbuntuNumberAnimation {
174− id: bottomFoldingArea
462+ id: fakeDragItemAnimation
175− anchors {
463+ target: fakeDragItem;
176− left: parent.left
464+ properties: "angle,offset";
177− right: parent.right
465+ to: 0
178− bottom: parent.bottom
466+ }
179− bottomMargin: launcherListView.bottomMargin
467+ }
472+ id: popoverComponent
477+ property string appId
478+ contentWidth: quickListColumn.width
480+ // FIXME: There's a bug in the Popover positioning that it covers the item in case it is rotated.
481+ // https://bugs.launchpad.net/ubuntu-ui-toolkit/+bug/1204470
482+ // For now, let's move the Popover around with callerMargin.
483+ // Remove popupMargin once the bug is fixed.
484+ property int popupMargin: root.inverted ? launcherListView.itemHeight : 0;
486+ callerMargin: units.gu(1) + popupMargin
491+ height: childrenRect.height
494+ model: popover.model
496+ ListItems.Standard {
497+ objectName: "quickListEntry" + index
498+ text: (model.clickable ? "" : "<b>") + model.label + (model.clickable ? "" : "</b>")
499+ highlightWhenPressed: model.clickable
501+ if (!model.clickable) {
504+ LauncherModel.quickListActionInvoked(appId, index);
505+ 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)