Merge lp:~ahayzen/music-app/remix-rewrite-column-flow into lp:music-app/remix

Proposed by Andrew Hayzen
Status: Merged
Approved by: Victor Thompson
Approved revision: 738
Merged at revision: 735
Proposed branch: lp:~ahayzen/music-app/remix-rewrite-column-flow
Merge into: lp:music-app/remix
Diff against target: 713 lines (+412/-147)
8 files modified
MusicArtists.qml (+1/-1)
MusicPlaylists.qml (+4/-4)
MusicStart.qml (+1/-1)
common/AlbumsPage.qml (+7/-0)
common/BlurredHeader.qml (+0/-2)
common/Card.qml (+4/-0)
common/CardView.qml (+11/-5)
common/ColumnFlow.qml (+384/-134)
To merge this branch: bzr merge lp:~ahayzen/music-app/remix-rewrite-column-flow
Reviewer Review Type Date Requested Status
Victor Thompson Approve
Ubuntu Phone Apps Jenkins Bot continuous-integration Approve
Review via email: mp+241338@code.launchpad.net

Commit message

* Rewrite the ColumnFlow.qml to use incubation to create objects and set offscreen cards as invisible

Description of the change

* Rewrite the ColumnFlow.qml to use incubation to create objects and set offscreen cards as invisible

This is a *complete* rewrite of the ColumnFlow, please test all instances of the CardView.

To post a comment you must log in.
Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Victor Thompson (vthompson) wrote :

2 inline comments so far.

Also, I've managed to get the Artist tab to do some bad things.

1. I've gotten this: http://i.imgur.com/qXAAxgb.png
2. Flicking downward in the Artists tab is initially "sticky". If you flick, it will stop quickly when it gets to the end of the currently loaded items.
3. I can cause the app to crash very easily in the Artists tab by over scrolling

review: Needs Fixing
Revision history for this message
Andrew Hayzen (ahayzen) wrote :

So we are going to investigate setting visible to false rather than destroying the old objects.

This should:
* Stop the thumbnails from reloading after they are destroyed
* Allow for easier implementation of adding/removing columns
* Still maintain the reduced time to initially load the thumbnails
* Simplify the code and reduce chances of corruption

Revision history for this message
Andrew Hayzen (ahayzen) wrote :

I've rewritten the component so that it incubates the objects as you scroll (meaning the thumbnails appear as fast as possible). Any cards that go out of the viewport are then set invisible to allow for increased performance. Resizing the window doesn't destroy the 'live' cards and instead reanchors them to their new positions so you get an animation.

Please rereview :)

Revision history for this message
Victor Thompson (vthompson) wrote :

Functionally I think this works really well. The code is looking good thus far--I have 2 minor inline comments.

Small typo, "Force any"

review: Needs Fixing
Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Needs Fixing (continuous-integration)
738. By Andrew Hayzen

* Fix for abstraction

Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Victor Thompson (vthompson) wrote :

LGTM! This makes the card views pretty snappy! :)

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'MusicArtists.qml'
2--- MusicArtists.qml 2014-11-01 23:25:42 +0000
3+++ MusicArtists.qml 2014-11-11 20:14:18 +0000
4@@ -23,7 +23,6 @@
5 import Ubuntu.Components.ListItems 1.0 as ListItem
6 import Ubuntu.MediaScanner 0.1
7 import Ubuntu.Thumbnailer 0.1
8-import QtMultimedia 5.0
9 import QtQuick.LocalStorage 2.0
10 import "meta-database.js" as Library
11 import "playlists.js" as Playlists
12@@ -37,6 +36,7 @@
13
14 CardView {
15 id: artistCardView
16+ getter: function (i) { return {"artist": artistsModel.get(i, ArtistsModel.RoleArtist)}; }
17 itemWidth: units.gu(12)
18 model: ArtistsModel {
19 id: artistsModel
20
21=== modified file 'MusicPlaylists.qml'
22--- MusicPlaylists.qml 2014-10-26 16:18:12 +0000
23+++ MusicPlaylists.qml 2014-11-11 20:14:18 +0000
24@@ -68,12 +68,12 @@
25 objectName: "playlistsCardView"
26 delegate: Card {
27 id: playlistCard
28- coverSources: Playlists.getPlaylistCovers(name)
29- primaryText: name
30- secondaryText: i18n.tr("%1 song", "%1 songs", count).arg(count)
31+ coverSources: Playlists.getPlaylistCovers(model.name)
32+ primaryText: model.name
33+ secondaryText: i18n.tr("%1 song", "%1 songs", model.count).arg(model.count)
34
35 onClicked: {
36- albumTracksModel.filterPlaylistTracks(name)
37+ albumTracksModel.filterPlaylistTracks(model.name)
38
39 var comp = Qt.createComponent("common/SongsPage.qml")
40 var songsPage = comp.createObject(mainPageStack,
41
42=== modified file 'MusicStart.qml'
43--- MusicStart.qml 2014-11-01 23:25:42 +0000
44+++ MusicStart.qml 2014-11-11 20:14:18 +0000
45@@ -79,7 +79,7 @@
46 secondaryText: model.type === "playlist" ? i18n.tr("Playlist") : (recentAlbumSongs.status === SongsModel.Ready && recentAlbumSongs.get(0, SongsModel.RoleModelData).author != "" ? recentAlbumSongs.get(0, SongsModel.RoleModelData).author : i18n.tr("Unknown Artist"))
47
48 onClicked: {
49- if (type === "playlist") {
50+ if (model.type === "playlist") {
51 albumTracksModel.filterPlaylistTracks(model.data)
52 }
53
54
55=== modified file 'common/AlbumsPage.qml'
56--- common/AlbumsPage.qml 2014-11-01 23:25:42 +0000
57+++ common/AlbumsPage.qml 2014-11-11 20:14:18 +0000
58@@ -39,6 +39,13 @@
59 anchors {
60 fill: parent
61 }
62+ getter: function (i) {
63+ return {
64+ "art": artistsModel.get(i, AlbumsModel.RoleArt),
65+ "artist": artistsModel.get(i, AlbumsModel.RoleArtist),
66+ "title": artistsModel.get(i, AlbumsModel.RoleTitle),
67+ };
68+ }
69 header: BlurredHeader {
70 rightColumn: Column {
71 spacing: units.gu(2)
72
73=== modified file 'common/BlurredHeader.qml'
74--- common/BlurredHeader.qml 2014-10-22 15:40:36 +0000
75+++ common/BlurredHeader.qml 2014-11-11 20:14:18 +0000
76@@ -54,7 +54,6 @@
77 left: coversImage.right
78 leftMargin: units.gu(2)
79 }
80- asynchronous: true
81 }
82
83 Loader {
84@@ -66,6 +65,5 @@
85 top: coversImage.bottom
86 topMargin: units.gu(1)
87 }
88- asynchronous: true
89 }
90 }
91
92=== modified file 'common/Card.qml'
93--- common/Card.qml 2014-10-21 20:11:35 +0000
94+++ common/Card.qml 2014-11-11 20:14:18 +0000
95@@ -23,6 +23,10 @@
96 id: card
97 height: cardColumn.childrenRect.height + 2 * bg.anchors.margins
98
99+ /* Required by ColumnFlow */
100+ property int index
101+ property var model
102+
103 property alias coverSources: coverGrid.covers
104 property alias primaryText: primaryLabel.text
105 property alias secondaryText: secondaryLabel.text
106
107=== modified file 'common/CardView.qml'
108--- common/CardView.qml 2014-10-28 03:32:01 +0000
109+++ common/CardView.qml 2014-11-11 20:14:18 +0000
110@@ -20,21 +20,25 @@
111
112
113 Flickable {
114+ id: cardViewFlickable
115 anchors {
116 fill: parent
117 }
118
119 // dont use flow.contentHeight as it is inaccurate due to height of labels
120 // changing as they load
121- contentHeight: headerLoader.childrenRect.height + flowContainer.height
122+ contentHeight: headerLoader.childrenRect.height + flow.contentHeight + flowContainer.anchors.margins * 2
123 contentWidth: width
124
125 property alias count: flow.count
126 property alias delegate: flow.delegate
127+ property var getter
128 property alias header: headerLoader.sourceComponent
129 property var model: flow.model
130 property real itemWidth: units.gu(15)
131
132+ onGetterChanged: flow.getter = getter // cannot use alias to set a function (must be var)
133+
134 onVisibleChanged: {
135 if (visible) { // only load model once CardView is visible
136 flow.model = model
137@@ -43,7 +47,6 @@
138
139 Loader {
140 id: headerLoader
141- asynchronous: true
142 visible: sourceComponent !== undefined
143 width: parent.width
144 }
145@@ -51,18 +54,21 @@
146 Item {
147 id: flowContainer
148 anchors {
149+ bottom: parent.bottom
150+ left: parent.left
151+ margins: units.gu(1)
152+ right: parent.right
153 top: headerLoader.bottom
154 }
155- height: flow.childrenRect.height + flow.anchors.margins * 2
156 width: parent.width
157
158 ColumnFlow {
159 id: flow
160 anchors {
161 fill: parent
162- margins: units.gu(1)
163 }
164- columns: parseInt(width / itemWidth)
165+ columns: parseInt(cardViewFlickable.width / itemWidth)
166+ flickable: cardViewFlickable
167 }
168 }
169
170
171=== modified file 'common/ColumnFlow.qml'
172--- common/ColumnFlow.qml 2014-10-13 16:56:55 +0000
173+++ common/ColumnFlow.qml 2014-11-11 20:14:18 +0000
174@@ -1,7 +1,6 @@
175 /*
176 * Copyright (C) 2014
177 * Andrew Hayzen <ahayzen@gmail.com>
178- * Michael Spencer <sonrisesoftware@gmail.com>
179 *
180 * This program is free software; you can redistribute it and/or modify
181 * it under the terms of the GNU General Public License as published by
182@@ -14,147 +13,398 @@
183 *
184 * You should have received a copy of the GNU General Public License
185 * along with this program. If not, see <http://www.gnu.org/licenses/>.
186- *
187- * Upstream location:
188- * https://github.com/iBeliever/ubuntu-ui-extras/blob/master/ColumnFlow.qml
189 */
190
191 import QtQuick 2.3
192
193-
194 Item {
195 id: columnFlow
196 property int columns: 1
197- property bool repeaterCompleted: false
198- property alias count: repeater.count
199- property alias model: repeater.model
200- property alias delegate: repeater.delegate
201+ property Flickable flickable
202+ property var model
203+ property Component delegate
204+
205+ property var getter: function (i) { return model.get(i); } // optional getter override (useful for music-app ms2 models)
206+
207+ property int buffer: units.gu(20)
208+ property var columnHeights: []
209+ property var columnHeightsMax: []
210+ property int columnWidth: parent.width / columns
211 property int contentHeight: 0
212-
213- onColumnsChanged: reEvalColumns()
214- onModelChanged: reEvalColumns()
215- onWidthChanged: updateWidths()
216-
217- function updateWidths() {
218- if (repeaterCompleted) {
219- var count = 0
220-
221- //add the first <column> elements
222- for (var i = 0; count < columns && i < columnFlow.children.length; i++) {
223- //print(i, count)
224- if (!columnFlow.children[i] || String(columnFlow.children[i]).indexOf("QQuickRepeater") == 0)
225- //|| !columnFlow.children[i].visible) // CUSTOM - view is invisible at start
226- continue
227-
228- columnFlow.children[i].width = width / columns
229-
230- count++
231- }
232- }
233- }
234-
235- function reEvalColumns() {
236- if (columnFlow.repeaterCompleted === false)
237- return
238-
239- if (columns === 0) {
240- contentHeight = 0
241- return
242- }
243-
244- var i, j
245- var columnHeights = new Array(columns);
246- var lastItem = new Array(columns)
247- var lastI = -1
248- var count = 0
249-
250- //add the first <column> elements
251- for (i = 0; count < columns && i < columnFlow.children.length; i++) {
252- // CUSTOM - ignore if has just been removed
253- if (i === repeater.removeHintIndex && columnFlow.children[i] === repeater.removeHintItem) {
254- continue
255- }
256-
257- if (!columnFlow.children[i] || String(columnFlow.children[i]).indexOf("QQuickRepeater") == 0)
258- //|| !columnFlow.children[i].visible) // CUSTOM - view is invisible at start
259- continue
260-
261- lastItem[count] = i
262-
263- columnHeights[count] = columnFlow.children[i].height
264- columnFlow.children[i].anchors.top = columnFlow.top
265- columnFlow.children[i].anchors.left = (lastI === -1 ? columnFlow.left : columnFlow.children[lastI].right)
266- columnFlow.children[i].anchors.right = undefined
267- columnFlow.children[i].width = columnFlow.width / columns
268-
269- lastI = i
270- count++
271- }
272-
273- //add the other elements
274- for (i = i; i < columnFlow.children.length; i++) {
275- var highestHeight = Number.MAX_VALUE
276- var newColumn = 0
277-
278- // CUSTOM - ignore if has just been removed
279- if (i === repeater.removeHintIndex && columnFlow.children[i] === repeater.removeHintItem) {
280- continue
281- }
282-
283- if (!columnFlow.children[i] || String(columnFlow.children[i]).indexOf("QQuickRepeater") == 0)
284- //|| !columnFlow.children[i].visible) // CUSTOM - view is invisible at start
285- continue
286-
287- // find the shortest column
288- for (j = 0; j < columns; j++) {
289- if (columnHeights[j] !== null && columnHeights[j] < highestHeight) {
290- newColumn = j
291- highestHeight = columnHeights[j]
292- }
293- }
294-
295- // add the element to the shortest column
296- columnFlow.children[i].anchors.top = columnFlow.children[lastItem[newColumn]].bottom
297- columnFlow.children[i].anchors.left = columnFlow.children[lastItem[newColumn]].left
298- columnFlow.children[i].anchors.right = columnFlow.children[lastItem[newColumn]].right
299-
300- lastItem[newColumn] = i
301- columnHeights[newColumn] += columnFlow.children[i].height
302- }
303-
304- var cHeight = 0
305-
306- for (i = 0; i < columnHeights.length; i++) {
307- if (columnHeights[i])
308- cHeight = Math.max(cHeight, columnHeights[i])
309- }
310-
311- contentHeight = cHeight
312- updateWidths()
313- }
314-
315- Repeater {
316- id: repeater
317- model: columnFlow.model
318- Component.onCompleted: {
319- columnFlow.repeaterCompleted = true
320- columnFlow.reEvalColumns()
321- }
322-
323- // Provide a hint of the removed item
324- property int removeHintIndex: -1 // CUSTOM
325- property var removeHintItem // CUSTOM
326-
327- onItemAdded: columnFlow.reEvalColumns() // CUSTOM - ms2 models are live
328- onItemRemoved: {
329- removeHintIndex = index
330- removeHintItem = item
331-
332- columnFlow.reEvalColumns() // CUSTOM - ms2 models are live
333-
334- // Set back to null to allow freeing of memory
335- removeHintIndex = -1
336- removeHintItem = undefined
337+ property int count: model === undefined ? 0 : model.count
338+ property var incubating: ({}) // incubating objects
339+ property var items: ({})
340+ property var itemToColumn: ({}) // cache of the columns of indexes
341+ property int lastIndex: 0 // the furtherest index loaded
342+ property bool restoring: false // is the view restoring?
343+ property var restoreItems: ({}) // when rebuilding items are stored here temporarily
344+
345+ onColumnWidthChanged: {
346+ if (restoring) {
347+ return;
348+ }
349+ else if (columns != columnHeights.length && visible) {
350+ // number of columns has changed so rebuild the columns
351+ rebuildColumns()
352+ } else { // column width has changed update visible items properties linked to columnWidth
353+ for (var column=0; column < columnHeights.length; column++) {
354+ for (var i in columnHeights[column]) {
355+ if (columnHeights[column].hasOwnProperty(i) && items.hasOwnProperty(i)) {
356+ items[i].width = columnWidth;
357+ items[i].x = column * columnWidth;
358+ }
359+ }
360+ }
361+
362+ ensureItemsVisible()
363+ }
364+ }
365+
366+ onCountChanged: {
367+ if (!visible) { // store changes for when visible
368+ if (count === 0 && lastIndex > -1) {
369+ lastIndex = -1;
370+ } else if (lastIndex > -1) {
371+ lastIndex = -(lastIndex + 2); // save the index to restore later
372+ }
373+ } else if (count === 0) { // likely the model is been reset so reset the view
374+ reset()
375+ } else { // likely new items in the model check if any can be shown
376+ append()
377+ }
378+ }
379+
380+ onVisibleChanged: {
381+ if (columns != columnHeights.length && visible) { // number of columns has changed while invisible so reset
382+ if (!restoring) {
383+ rebuildColumns()
384+ }
385+ } else if (lastIndex < 0 && visible) { // restore from count change
386+ if (lastIndex === -1) {
387+ reset()
388+ } else {
389+ lastIndex = (-lastIndex) - 2
390+ }
391+
392+ append()
393+ }
394+ }
395+
396+ // Append a new row of items if possible
397+ function append()
398+ {
399+ // Do not allow append to run if incubating
400+ if (isIncubating() || restoring) {
401+ return;
402+ }
403+
404+ // get the columns in order
405+ var columnsByHeight = getColumnsByHeight();
406+ var workDone = false;
407+
408+ // check if a new item in each column is possible
409+ for (var i=0; i < columnsByHeight.length; i++) {
410+ var y = columnHeightsMax[columnsByHeight[i]];
411+
412+ // build new object in column if possible
413+ if (count > 0 && lastIndex < count && inViewport(y, 0)) {
414+ incubateObject(lastIndex++, columnsByHeight[i], getMaxInColumn(columnsByHeight[i]), append);
415+ workDone = true
416+ } else {
417+ break;
418+ }
419+ }
420+
421+ if (!workDone) { // last iteration over append so visible ensure items are correct
422+ ensureItemsVisible();
423+ }
424+ }
425+
426+ // Cache the size of the columns for use later
427+ function cacheColumnHeights()
428+ {
429+ columnHeightsMax = [];
430+
431+ for (var i=0; i < columnHeights.length; i++) {
432+ var sum = 0;
433+
434+ for (var j in columnHeights[i]) {
435+ sum += columnHeights[i][j];
436+ }
437+
438+ columnHeightsMax.push(sum);
439+ }
440+
441+ if (!restoring) { // when not restoring otherwise user will be pushed to the top of the view
442+ // set the height of columnFlow to max column (for flickable contentHeight)
443+ contentHeight = Math.max.apply(null, columnHeightsMax);
444+ }
445+ }
446+
447+ // Recache the visible items heights (due to a change in their height)
448+ function cacheVisibleItemsHeights()
449+ {
450+ for (var i in items) {
451+ if (items.hasOwnProperty(i)) {
452+ columnHeights[itemToColumn[i]][i] = items[i].height;
453+ }
454+ }
455+
456+ cacheColumnHeights();
457+ }
458+
459+ // Ensures that the correct items are visible
460+ function ensureItemsVisible()
461+ {
462+ for (var i in items) {
463+ if (items.hasOwnProperty(i)) {
464+ items[i].visible = inViewport(items[i].y, items[i].height)
465+ }
466+ }
467+ }
468+
469+ // Return if there are incubating objects
470+ function isIncubating()
471+ {
472+ for (var i in incubating) {
473+ if (incubating.hasOwnProperty(i)) {
474+ return true;
475+ }
476+ }
477+
478+ return false;
479+ }
480+
481+ // Run after incubation to store new column height and call any further append/restores
482+ function finishIncubation(index, callback)
483+ {
484+ var obj = incubating[index].object;
485+ delete incubating[index];
486+
487+ obj.heightChanged.connect(cacheVisibleItemsHeights) // if the height changes recache
488+
489+ // Ensure properties linked to columnWidth are correct (as width may still be changing)
490+ obj.x = itemToColumn[index] * columnWidth;
491+ obj.width = columnWidth;
492+
493+ items[index] = obj;
494+
495+ columnHeights[itemToColumn[index]][index] = obj.height; // ensure height is the latest
496+
497+ if (!isIncubating()) {
498+ cacheColumnHeights();
499+
500+ // Check if there is any more work to be done (append or restore)
501+ callback();
502+ }
503+ }
504+
505+ // Force any incubation to finish
506+ function forceIncubationCompletion()
507+ {
508+ for (var i in incubating) {
509+ if (incubating.hasOwnProperty(i)) {
510+ incubating[i].forceCompletion()
511+ }
512+ }
513+ }
514+
515+ // Get the column index in order of height
516+ function getColumnsByHeight()
517+ {
518+ var columnsByHeight = [];
519+
520+ for (var i=0; i < columnHeightsMax.length; i++) {
521+ var min = undefined;
522+ var index = -1;
523+
524+ // Find the smallest column that has not been found yet
525+ for (var j=0; j < columnHeightsMax.length; j++) {
526+ if (columnsByHeight.indexOf(j) === -1 && (min === undefined || columnHeightsMax[j] < min)) {
527+ min = columnHeightsMax[j];
528+ index = j;
529+ }
530+ }
531+
532+ columnsByHeight.push(index);
533+ }
534+
535+ return columnsByHeight;
536+ }
537+
538+ // Get the highest index for a column
539+ function getMaxInColumn(column)
540+ {
541+ var max;
542+
543+ for (var i in columnHeights[column]) {
544+ if (columnHeights[column].hasOwnProperty(i)) {
545+ i = parseInt(i);
546+
547+ if (items.hasOwnProperty(i)) {
548+ if (i > max || max === undefined) {
549+ max = i;
550+ }
551+ }
552+ }
553+ }
554+
555+ return max;
556+ }
557+
558+ // Incubate an object for creation
559+ function incubateObject(index, column, anchorIndex, callback)
560+ {
561+ // Load parameters to send to the object on creation
562+ var params = {
563+ "anchors.top": anchorIndex === undefined ? parent.top : items[anchorIndex].bottom,
564+ index: index,
565+ model: getter(index),
566+ width: columnWidth,
567+ x: column * columnWidth
568+ };
569+
570+ // Start incubating and cache the column
571+ incubating[index] = delegate.incubateObject(parent, params);
572+ itemToColumn[index] = column;
573+
574+ if (incubating[index].status != Component.Ready) {
575+ incubating[index].onStatusChanged = function(status) {
576+ if (status == Component.Ready) {
577+ finishIncubation(index, callback)
578+ }
579+ }
580+ } else {
581+ finishIncubation(index, callback)
582+ }
583+ }
584+
585+ // Detect if a loaded object is in the viewport with a buffer
586+ function inViewport(y, height)
587+ {
588+ return flickable.contentY - buffer < y + height && y < flickable.contentY + flickable.height + buffer;
589+ }
590+
591+ // Number of columns has changed rebuild with live items
592+ function rebuildColumns()
593+ {
594+ restoring = true;
595+ var i;
596+
597+ forceIncubationCompletion()
598+
599+ columnHeights = []
600+ columnHeightsMax = []
601+
602+ for (i=0; i < columns; i++) {
603+ columnHeights.push({});
604+ columnHeightsMax.push(0);
605+ }
606+
607+ lastIndex = 0;
608+
609+ restoreItems = items;
610+ items = {};
611+
612+ restoreExisting()
613+
614+ restoring = false;
615+
616+ cacheColumnHeights(); // rebuilds contentHeight
617+
618+ // If the columns have changed while the view was locked rerun
619+ if (columns != columnHeights.length && visible) {
620+ rebuildColumns()
621+ } else {
622+ append() // check if any new items can be added
623+ }
624+ }
625+
626+ // Restores existing items into potentially new positions
627+ function restoreExisting()
628+ {
629+ var i;
630+
631+ // get the columns in order
632+ var columnsByHeight = getColumnsByHeight();
633+ var workDone = false;
634+
635+ // check if a new item in each column is possible
636+ for (i=0; i < columnsByHeight.length; i++) {
637+ var column = columnsByHeight[i];
638+
639+ // build new object in column if possible
640+ if (count > 0 && lastIndex < count) {
641+ if (restoreItems.hasOwnProperty(lastIndex)) {
642+ var item = restoreItems[lastIndex];
643+ var maxInColumn = getMaxInColumn(column); // get lowest item in column
644+
645+ itemToColumn[lastIndex] = column;
646+ columnHeights[column][lastIndex] = item.height; // ensure height is the latest
647+
648+ // Rebuild item properties
649+ item.anchors.bottom = undefined
650+ item.anchors.top = maxInColumn === undefined ? parent.top : items[maxInColumn].bottom;
651+ item.x = column * columnWidth;
652+ item.visible = inViewport(item.y, item.height);
653+
654+ // Migrate item from restoreItems to items
655+ items[lastIndex] = item;
656+ delete restoreItems[lastIndex];
657+
658+ // set after restore as height will likely change causing cacheVisibleItemsHeights to be run
659+ item.width = columnWidth;
660+
661+ cacheColumnHeights(); // ensure column heights are up to date
662+
663+ lastIndex++;
664+ workDone = true;
665+ }
666+ } else {
667+ break;
668+ }
669+ }
670+
671+ if (workDone) {
672+ restoreExisting() // if work done then check if any more is needed
673+ } else {
674+ restoreItems = {}; // ensure restoreItems is empty
675+ }
676+ }
677+
678+ // Reset the column flow
679+ function reset()
680+ {
681+ forceIncubationCompletion()
682+
683+ // Destroy any old items
684+ for (var j in items) {
685+ if (items.hasOwnProperty(j)) {
686+ items[j].destroy()
687+ }
688+ }
689+
690+ // Reset and rebuild the variables
691+ items = ({})
692+ lastIndex = 0
693+
694+ columnHeights = []
695+
696+ for (var k=0; k < columns; k++) {
697+ columnHeights.push({})
698+ }
699+
700+ cacheColumnHeights()
701+
702+ contentHeight = 0
703+ }
704+
705+ Connections {
706+ target: flickable
707+ onContentYChanged: {
708+ append() // Append any new items (scrolling down)
709+
710+ ensureItemsVisible()
711 }
712 }
713 }

Subscribers

People subscribed via source and target branches