@@ -21,11 +21,11 @@ import "../Components/ListItems" as ListItems
2121
2222ScopeView {
2323 id: scopeView
24− readonly property alias previewShown: previewLoader.onScreen
24+ readonly property alias previewShown: previewListView.onScreen
2525
2626 onIsCurrentChanged: {
2727 pageHeader.resetSearch();
28− previewLoader.open = false;
28+ previewListView.open = false;
2929 }
3030
3131 onMovementStarted: categoryView.showHeader()
@@ -52,22 +52,29 @@ ScopeView {
5252
5353 Connections {
5454 target: scopeView.scope
55− onShowDash: previewLoader.open = false;
56− onHideDash: previewLoader.open = false;
55+ onShowDash: previewListView.open = false;
56+ onHideDash: previewListView.open = false;
5757 }
5858
5959 ScopeListView {
6060 id: categoryView
6161 anchors.fill: parent
6262 model: scopeView.categories
63− forceNoClip: previewLoader.onScreen
63+ forceNoClip: previewListView.onScreen
6464
6565 onAtYEndChanged: if (atYEnd) endReached()
6666 onMovingChanged: if (moving && atYEnd) endReached()
6767
6868 property string expandedCategoryId: ""
69+ signal correctExpandedCategory();
70+
71+ Behavior on contentY {
72+ enabled: previewListView.open
73+ UbuntuNumberAnimation {}
74+ }
6975
7076 delegate: ListItems.Base {
77+ id: rendererDelegate
7178 highlightWhenPressed: false
7279
7380 readonly property bool expandable: rendererLoader.item ? rendererLoader.item.expandable : false
@@ -104,33 +111,50 @@ ScopeView {
104111 Connections {
105112 target: rendererLoader.item
106113 onClicked: {
114+ // Prepare the preview in case activate() triggers a preview only
107115 effect.positionPx = mapToItem(categoryView, 0, itemY).y
108− scopeView.scope.activate(delegateItem.model.uri,
109− delegateItem.model.icon,
110− delegateItem.model.category,
111− 0,
112− delegateItem.model.mimetype,
113− delegateItem.model.title,
114− delegateItem.model.comment,
115− delegateItem.model.dndUri,
116− delegateItem.model.metadata)
116+ previewListView.categoryId = categoryId
117+ previewListView.categoryDelegate = rendererLoader.item
118+ previewListView.model = model;
119+ previewListView.init = true;
120+ previewListView.currentIndex = index;
121+
122+ var item = model.get(index);
123+
124+ if ((scopeView.scope.id == "applications.scope" && categoryId == "installed")
125+ || (scopeView.scope.id == "home.scope" && categoryId == "applications.scope")) {
126+ scopeView.scope.activate(item.uri, item.icon, item.category, 0, item.mimetype, item.title,
127+ item.comment, item.dndUri, item.metadata)
128+ } else {
129+ previewListView.open = true
130+
131+ scopeView.scope.preview( item.uri, item.icon, item.category, 0, item.mimetype, item.title,
132+ item.comment, item.dndUri, item.metadata)
133+ }
117134 }
118135 onPressAndHold: {
119136 effect.positionPx = mapToItem(categoryView, 0, itemY).y
120− scopeView.scope.preview( delegateItem.model.uri,
121− delegateItem.model.icon,
122− delegateItem.model.category,
123− 0,
124− delegateItem.model.mimetype,
125− delegateItem.model.title,
126− delegateItem.model.comment,
127− delegateItem.model.dndUri,
128− delegateItem.model.metadata)
137+ previewListView.categoryId = categoryId
138+ previewListView.categoryDelegate = rendererLoader.item
139+ previewListView.model = model;
140+ previewListView.init = true;
141+ previewListView.currentIndex = index;
142+ previewListView.open = true
143+
144+ var item = model.get(index)
145+ scopeView.scope.preview( item.uri, item.icon, item.category, 0, item.mimetype, item.title,
146+ item.comment, item.dndUri, item.metadata)
129147 }
130148 }
131149 Connections {
132150 target: categoryView
133151 onExpandedCategoryIdChanged: {
152+ collapseAllButExpandedCategory();
153+ }
154+ onCorrectExpandedCategory: {
155+ collapseAllButExpandedCategory();
156+ }
157+ function collapseAllButExpandedCategory() {
134158 var item = rendererLoader.item;
135159 if (item.expandable) {
136160 var shouldFilter = categoryId != categoryView.expandedCategoryId;
@@ -138,13 +162,15 @@ ScopeView {
138162 // If the filter animation will be seen start it, otherwise, just flip the switch
139163 var shrinkingVisible = shouldFilter && y + item.collapsedHeight < categoryView.height;
140164 var growingVisible = !shouldFilter && y + height < categoryView.height;
141− if (shrinkingVisible || growingVisible) {
142− item.startFilterAnimation(shouldFilter)
143− } else {
144− item.filter = shouldFilter;
145− }
146− if (!shouldFilter) {
147− categoryView.maximizeVisibleArea(index, item.uncollapsedHeight);
165+ if (!previewListView.open || !shouldFilter) {
166+ if (shrinkingVisible || growingVisible) {
167+ item.startFilterAnimation(shouldFilter)
168+ } else {
169+ item.filter = shouldFilter;
170+ }
171+ if (!shouldFilter && !previewListView.open) {
172+ categoryView.maximizeVisibleArea(index, item.uncollapsedHeight);
173+ }
148174 }
149175 }
150176 }
@@ -222,18 +248,18 @@ ScopeView {
222248 topOpacity: (1 - gap * 1.2)
223249 bottomGapPx: positionPx + gap * (targetBottomGapPx - positionPx)
224250 bottomOverflow: units.gu(20)
225− bottomOpacity: 1 - (gap * 0.8)
251+ live: true
226252
227253 property int targetBottomGapPx: height - units.gu(8) - bottomOverflow
228− property real gap: previewLoader.open ? 1.0 : 0.0
254+ property real gap: previewListView.open ? 1.0 : 0.0
229255
230256 Behavior on gap {
231257 NumberAnimation {
232258 duration: 200
233259 easing.type: Easing.InOutQuad
234260 onRunningChanged: {
235− if (!previewLoader.open && !running) {
236− previewLoader.onScreen = false
261+ if (!previewListView.open && !running) {
262+ previewListView.onScreen = false
237263 }
238264 }
239265 }
@@ -243,8 +269,18 @@ ScopeView {
243269 Connections {
244270 target: scopeView.scope
245271 onPreviewReady: {
246− previewLoader.previewData = preview
247− previewLoader.open = true
272+ if (previewListView.init) {
273+ // Preview was triggered because of a click on the item. Need to expand now.
274+ if (!previewListView.open) {
275+ previewListView.open = true
276+ }
277+
278+ var index = previewListView.currentIndex
279+ previewListView.currentIndex = -1
280+ previewListView.currentIndex = index
281+ previewListView.init = false
282+ }
283+ previewListView.currentItem.previewData = preview
248284 }
249285 }
250286
@@ -252,18 +288,9 @@ ScopeView {
252288 id: previewDelegateMapper
253289 }
254290
255− Connections {
256− ignoreUnknownSignals: true
257− target: previewLoader.valid ? previewLoader.item : null
258− onClose: {
259− previewLoader.open = false
260− }
261− }
262−
263− Loader {
264− objectName: "previewLoader"
265− id: previewLoader
266− property var previewData
291+ ListView {
292+ id: previewListView
293+ objectName: "previewListView"
267294 height: effect.bottomGapPx - effect.topGapPx
268295 anchors {
269296 top: parent.top
@@ -271,32 +298,151 @@ ScopeView {
271298 left: parent.left
272299 right: parent.right
273300 }
274− source: onScreen ? previewDelegateMapper.map(previewLoader.previewData.rendererName) : ""
301+ orientation: ListView.Horizontal
302+ highlightRangeMode: ListView.StrictlyEnforceRange
303+ snapMode: ListView.SnapOneItem
304+ boundsBehavior: Flickable.DragAndOvershootBounds
305+ highlightMoveDuration: 250
306+ flickDeceleration: units.gu(625)
307+ maximumFlickVelocity: width * 5
308+ cacheBuffer: 0
309+
310+ // To be set before opening the preview
311+ property string categoryId: ""
312+ property var categoryDelegate
313+
314+ // because the ListView is built asynchronous, setting the
315+ // currentIndex directly won't work. We need to refresh it
316+ // when the first preview is ready to be displayed.
317+ property bool init: true
318+
319+ // Used internally
320+ property int oldRow: -1
321+ property bool categoryWasFiltered: false
322+ property int initialContenY: 0
323+
324+ onCurrentIndexChanged: {
325+ var row = Math.floor(currentIndex / categoryDelegate.columns);
326+ if (!init && model !== undefined) {
327+ var item = model.get(currentIndex)
328+ scopeView.scope.preview( item.uri, item.icon, item.category, 0, item.mimetype, item.title, item.comment, item.dndUri, item.metadata)
329+ if (oldRow != -1) {
330+ if (row < oldRow) {
331+ categoryView.contentY -= categoryDelegate.cellHeight
332+ } else if (row > oldRow){
333+ categoryView.contentY += categoryDelegate.cellHeight
334+ }
335+ }
336+ }
337+ oldRow = row;
338+ if (categoryDelegate.collapsedRowCount <= row) {
339+ categoryView.expandedCategoryId = categoryId
340+ }
341+
342+ if (open) {
343+ categoryDelegate.highlightIndex = currentIndex
344+ }
345+ }
275346
276347 property bool open: false
277348 property bool onScreen: false
278− property bool valid: item !== null
279349
280350 onOpenChanged: {
281351 if (open) {
282352 onScreen = true
353+ categoryDelegate.highlightIndex = currentIndex
354+ } else {
355+ model = undefined
356+ categoryView.correctExpandedCategory();
357+ categoryDelegate.highlightIndex = -1
283358 }
284359 }
285360
286− onLoaded: {
287− item.previewData = Qt.binding(function() { return previewLoader.previewData })
361+ Rectangle {
362+ anchors.fill: parent
363+ color: Qt.rgba(0, 0, 0, .3)
364+ }
365+
366+ delegate: Loader {
367+ id: previewLoader
368+ objectName: "previewLoader"
369+ height: previewListView.height
370+ width: previewListView.width
371+ asynchronous: true
372+ source: previewListView.onScreen ?
373+ (previewData !== undefined ? previewDelegateMapper.map(previewData.rendererName) : "DashPreviewPlaceholder.qml") : ""
374+
375+ onPreviewDataChanged: {
376+ if (previewData !== undefined && source.toString().indexOf("DashPreviewPlaceholder.qml") != -1) {
377+ previewLoader.opacity = 0;
378+ }
379+ }
380+
381+ onSourceChanged: {
382+ if (previewData !== undefined) {
383+ fadeIn.start()
384+ }
385+ }
386+
387+ PropertyAnimation {
388+ id: fadeIn
389+ target: previewLoader
390+ property: "opacity"
391+ from: 0.0
392+ to: 1.0
393+ duration: UbuntuAnimation.BriskDuration
394+ }
395+
396+ property var previewData
397+ property bool valid: item !== null
398+
399+ onLoaded: {
400+ if (previewListView.onScreen && previewData !== undefined) {
401+ item.previewData = Qt.binding(function() { return previewData })
402+ }
403+ }
404+
405+ Connections {
406+ ignoreUnknownSignals: true
407+ target: item
408+ onClose: {
409+ previewListView.open = false
410+ }
411+ }
412+ }
413+ }
414+
415+ Image {
416+ anchors {
417+ top: previewListView.bottom
418+ left: parent.left
419+ leftMargin: previewListView.categoryDelegate.highlightCentered ?
420+ (parent.width - width) / 2 :
421+ (highlightColumn * cellWidth) + (-width + cellWidth + margins) / 2
422+ Behavior on leftMargin {
423+ UbuntuNumberAnimation {}
424+ }
288425 }
426+ height: units.gu(1)
427+ width: units.gu(2)
428+ property int columns: previewListView.categoryDelegate ? previewListView.categoryDelegate.columns : 1
429+ property int highlightColumn: previewListView.currentIndex % columns
430+ property int cellWidth: previewListView.categoryDelegate ? previewListView.categoryDelegate.cellWidth : 0
431+ property int margins: previewListView.categoryDelegate ? previewListView.categoryDelegate.margins : 0
432+ opacity: previewListView.open ? .5 : 0
433+
434+ source: "graphics/tooltip_arrow.png"
289435 }
290436
291437 // TODO: Move as InverseMouseArea to DashPreview
292438 MouseArea {
293− enabled: previewLoader.onScreen
439+ enabled: previewListView.onScreen
294440 anchors {
295441 fill: parent
296442 topMargin: effect.bottomGapPx
297443 }
298444 onClicked: {
299− previewLoader.open = false;
445+ previewListView.open = false;
300446 }
301447 }
302448}