@@ -18,27 +18,50 @@ import QtQuick 2.0
18import Ubuntu.Components 0.1
18import Ubuntu.Components 0.1
19import "carousel.js" as CarouselJS
19import "carousel.js" as CarouselJS
21+/*! The Carousel component presents the items of a model in a carousel view. It's similar to a
22+ cover flow. But it stops at it's boundaries (therefore no PathView is used).
22 id: carousel
25 id: carousel
24− clip: true // FIXME ?
27+ /// The component to be used as delegate. This component has to be derived from BaseCarouselDelegate
25 property Component itemComponent
28 property Component itemComponent
29+ /// Model for the Carousel, which has to be a model usable by a ListView
26− property var model
30+ property alias model: listView.model
31+ /// A minimal width of a tile can be set here. Per default a best fit will be calculated
27− property alias minimumTileWidth: flickable.minimumTileWidth
32+ property alias minimumTileWidth: listView.minimumTileWidth
33+ /// Sets the number of tiles that are visible
28− property alias pathItemCount: flickable.pathItemCount
34+ property alias pathItemCount: listView.pathItemCount
35+ /// Aspect ratio of the tiles width/height
29− property alias tileAspectRatio: flickable.tileAspectRatio
36+ property alias tileAspectRatio: listView.tileAspectRatio
30− property int cacheBuffer: 0
37+ /// Used to cache some delegates for performance reasons. See the ListView documentation for details
38+ property alias cacheBuffer: listView.cacheBuffer
39+ /// Width of the "draw buffer" in pixel. The drawBuffer is an additional area at start/end where
40+ /// items drawn, even if it is not in the visible area.
41+ /// cacheBuffer controls only the to retain delegates outside the visible area (and is used on top of the drawBuffer)
42+ /// see https://bugreports.qt-project.org/browse/QTBUG-29173
43+ property int drawBuffer: width / pathItemCount // an "ok" value - but values used from the listView cause loops
44+ /// The selected item can be shown in a different size controlled by selectedItemScaleFactor
31 property real selectedItemScaleFactor: 1.1
45 property real selectedItemScaleFactor: 1.1
47+ /// Emitted when the user clicked on an item
48+ /// @param index is the index of the clicked item
49+ /// @param delegateItem is the clicked component/delegate itself
50+ /// @param itemY is y of the clicked delegate
33 signal clicked(int index, var delegateItem, real itemY)
51 signal clicked(int index, var delegateItem, real itemY)
35− implicitHeight: flickable.tileHeight * selectedItemScaleFactor
53+ implicitHeight: listView.tileHeight * selectedItemScaleFactor
37− /* TODO: evaluate if the component could be more efficient with a ListView,
55+ /* Basic idea behind the carousel effect is to move the items of the delegates (compacting /stuffing them).
56+ One drawback is, that more delegates have to be drawn than usually. As some items are moved from the
57+ invisible to the visible area. Setting the cacheBuffer does not fix this.
38− using this technique https://bugreports.qt-project.org/browse/QTBUG-29173 */
58+ See https://bugreports.qt-project.org/browse/QTBUG-29173
59+ Therefore the ListView has negative left and right anchors margins, and in addition a header
60+ and footer item to compensate that.
40− Flickable {
62+ The scaling of the items is controlled by the variable continuousIndex, described below. */
41− id: flickable
63+ ListView {
43 property real minimumTileWidth: 0
66 property real minimumTileWidth: 0
44 property real newContentX: -1
67 property real newContentX: -1
@@ -58,20 +81,27 @@ Item {
58 - 'gapToEndPhase' gap in pixels between middle and end phase
81 - 'gapToEndPhase' gap in pixels between middle and end phase
59 - 'kGapEnd' constant used to calculate 'continuousIndex' in end phase
82 - 'kGapEnd' constant used to calculate 'continuousIndex' in end phase
60 - 'kMiddleIndex' constant used to calculate 'continuousIndex' in middle phase
83 - 'kMiddleIndex' constant used to calculate 'continuousIndex' in middle phase
61− - 'kXBeginningEnd' constant used to calculate 'continuousIndex' in beginning and end phase. */
84+ - 'kXBeginningEnd' constant used to calculate 'continuousIndex' in beginning and end phase
85+ - 'realContentWidth' the width of all the delegates only (without header/footer)
86+ - 'realContentX' the 'contentX' of the listview ignoring the 'drawBuffer'
87+ - 'realWidth' the 'width' of the listview, as it is used as component. */
63− readonly property real gapToMiddlePhase: Math.min(width / 2 - tileWidth / 2, (contentWidth - width) / 2)
89+ readonly property real gapToMiddlePhase: Math.min(realWidth / 2 - tileWidth / 2, (realContentWidth - realWidth) / 2)
64− readonly property real gapToEndPhase: contentWidth - width - gapToMiddlePhase
90+ readonly property real gapToEndPhase: realContentWidth - realWidth - gapToMiddlePhase
65 readonly property real kGapEnd: kMiddleIndex * (1 - gapToEndPhase / gapToMiddlePhase)
91 readonly property real kGapEnd: kMiddleIndex * (1 - gapToEndPhase / gapToMiddlePhase)
66− readonly property real kMiddleIndex: (width / 2) / tileWidth - 0.5
92+ readonly property real kMiddleIndex: (realWidth / 2) / tileWidth - 0.5
67 readonly property real kXBeginningEnd: 1 / tileWidth + kMiddleIndex / gapToMiddlePhase
93 readonly property real kXBeginningEnd: 1 / tileWidth + kMiddleIndex / gapToMiddlePhase
94+ readonly property real maximumItemTranslation: (listView.tileWidth * 3) / listView.scaleFactor
95+ readonly property real realContentWidth: contentWidth - 2 * carousel.drawBuffer
96+ readonly property real realContentX: contentX + carousel.drawBuffer
68− readonly property real realPathItemCount: Math.min(width / tileWidth, pathItemCount)
97+ readonly property real realPathItemCount: Math.min(realWidth / tileWidth, pathItemCount)
98+ readonly property real realWidth: carousel.width
69− readonly property real referenceGapToMiddlePhase: width / 2 - tileWidth / 2
99+ readonly property real referenceGapToMiddlePhase: realWidth / 2 - tileWidth / 2
70 readonly property real referencePathItemCount: referenceWidth / referenceTileWidth
100 readonly property real referencePathItemCount: referenceWidth / referenceTileWidth
71 readonly property real referenceWidth: 848
101 readonly property real referenceWidth: 848
72 readonly property real referenceTileWidth: 175
102 readonly property real referenceTileWidth: 175
73 readonly property real scaleFactor: tileWidth / referenceTileWidth
103 readonly property real scaleFactor: tileWidth / referenceTileWidth
74− readonly property real tileWidth: Math.max(width / pathItemCount, minimumTileWidth)
104+ readonly property real tileWidth: Math.max(realWidth / pathItemCount, minimumTileWidth)
75 readonly property real tileHeight: tileWidth / tileAspectRatio
105 readonly property real tileHeight: tileWidth / tileAspectRatio
76 readonly property real translationXViewFactor: 0.2 * (referenceGapToMiddlePhase / gapToMiddlePhase)
106 readonly property real translationXViewFactor: 0.2 * (referenceGapToMiddlePhase / gapToMiddlePhase)
77 readonly property real verticalMargin: (parent.height - tileHeight) / 2
107 readonly property real verticalMargin: (parent.height - tileHeight) / 2
@@ -81,20 +111,36 @@ Item {
81 fill: parent
111 fill: parent
82 topMargin: verticalMargin
112 topMargin: verticalMargin
83 bottomMargin: verticalMargin
113 bottomMargin: verticalMargin
114+ // extending the "drawing area"
115+ leftMargin: -carousel.drawBuffer
116+ rightMargin: -carousel.drawBuffer
85− contentWidth: view.width
118+
86− contentHeight: height
119+ /* The header and footer help to "extend" the area, the listview draws items.
120+ This together with anchors.leftMargin and anchors.rightMargin. */
122+ width: carousel.drawBuffer
123+ height: listView.tileHeight
126+ width: carousel.drawBuffer
127+ height: listView.tileHeight
87 boundsBehavior: Flickable.StopAtBounds
130 boundsBehavior: Flickable.StopAtBounds
131+ cacheBuffer: carousel.cacheBuffer
88− flickDeceleration: Math.max(1500 * Math.pow(width / referenceWidth, 1.5), 1500) // 1500 is platform default
132+ flickDeceleration: Math.max(1500 * Math.pow(realWidth / referenceWidth, 1.5), 1500) // 1500 is platform default
89− maximumFlickVelocity: Math.max(2500 * Math.pow(width / referenceWidth, 1.5), 2500) // 2500 is platform default
133+ maximumFlickVelocity: Math.max(2500 * Math.pow(realWidth / referenceWidth, 1.5), 2500) // 2500 is platform default
134+ orientation: ListView.Horizontal
91 function itemClicked(index, delegateItem) {
136 function itemClicked(index, delegateItem) {
92 var x = CarouselJS.getXFromContinuousIndex(index,
137 var x = CarouselJS.getXFromContinuousIndex(index,
93− width,
138+ realWidth,
94− contentWidth,
139+ realContentWidth,
95 tileWidth,
140 tileWidth,
96 gapToMiddlePhase,
141 gapToMiddlePhase,
97− gapToEndPhase)
142+ gapToEndPhase,
143+ carousel.drawBuffer)
99 if (Math.abs(x - contentX) < 1) {
145 if (Math.abs(x - contentX) < 1) {
100 /* We're clicking the selected item and
146 /* We're clicking the selected item and
@@ -117,22 +163,22 @@ Item {
117 newContentX = -1
163 newContentX = -1
119 onMovementEnded: {
165 onMovementEnded: {
120− if (contentX > 0 && contentX < contentWidth - width)
166+ if (realContentX > 0 && realContentX < realContentWidth - realWidth)
121 stepAnimation.start()
167 stepAnimation.start()
124 SmoothedAnimation {
170 SmoothedAnimation {
125 id: stepAnimation
171 id: stepAnimation
127− target: flickable
173+ target: listView
128 property: "contentX"
174 property: "contentX"
129− from: flickable.contentX
130− to: CarouselJS.getXFromContinuousIndex(view.selectedIndex,
175+ to: CarouselJS.getXFromContinuousIndex(listView.selectedIndex,
131− flickable.width,
176+ listView.realWidth,
132− flickable.contentWidth,
177+ listView.realContentWidth,
133− flickable.tileWidth,
178+ listView.tileWidth,
134− flickable.gapToMiddlePhase,
179+ listView.gapToMiddlePhase,
135− flickable.gapToEndPhase)
180+ listView.gapToEndPhase,
181+ carousel.drawBuffer)
136 duration: 450
182 duration: 450
137 velocity: 200
183 velocity: 200
138 easing.type: Easing.InOutQuad
184 easing.type: Easing.InOutQuad
@@ -142,112 +188,96 @@ Item {
142 id: newContentXAnimation
188 id: newContentXAnimation
144 NumberAnimation {
190 NumberAnimation {
145− target: flickable
191+ target: listView
146 property: "contentX"
192 property: "contentX"
147− from: flickable.contentX
193+ from: listView.contentX
148− to: flickable.newContentX
194+ to: listView.newContentX
149 duration: 300
195 duration: 300
150 easing.type: Easing.InOutQuad
196 easing.type: Easing.InOutQuad
152 ScriptAction {
198 ScriptAction {
153− script: flickable.newContentX = -1
199+ script: listView.newContentX = -1
160− readonly property int selectedIndex: Math.round(continuousIndex)
203+ readonly property int selectedIndex: Math.round(continuousIndex)
161− readonly property real continuousIndex: CarouselJS.getContinuousIndex(flickable.contentX,
204+ readonly property real continuousIndex: CarouselJS.getContinuousIndex(listView.realContentX,
162− flickable.tileWidth,
205+ listView.tileWidth,
163− flickable.gapToMiddlePhase,
206+ listView.gapToMiddlePhase,
164− flickable.gapToEndPhase,
207+ listView.gapToEndPhase,
165− flickable.kGapEnd,
208+ listView.kGapEnd,
166− flickable.kMiddleIndex,
209+ listView.kMiddleIndex,
167− flickable.kXBeginningEnd)
210+ listView.kXBeginningEnd)
169− height: parent.height
212+ property real viewTranslation: CarouselJS.getViewTranslation(listView.realContentX,
170− anchors.verticalCenter: parent.verticalCenter
213+ listView.tileWidth,
214+ listView.gapToMiddlePhase,
215+ listView.gapToEndPhase,
216+ listView.translationXViewFactor)
219+ property bool explicitlyScaled: explicitScaleFactor == carousel.selectedItemScaleFactor
220+ property real explicitScaleFactor: explicitScale ? carousel.selectedItemScaleFactor : 1.0
221+ readonly property bool explicitScale: (!listView.moving ||
222+ listView.realContentX <= 0 ||
223+ listView.realContentX >= listView.realContentWidth - listView.realWidth) &&
224+ listView.newContentX < 0 &&
225+ index === listView.selectedIndex
226+ readonly property real cachedTiles: listView.realPathItemCount + carousel.drawBuffer / listView.tileWidth
227+ readonly property real distance: listView.continuousIndex - index
228+ readonly property real itemTranslationScale: CarouselJS.getItemScale(0.5,
229+ (index + 0.5), // good approximation of scale while changing selected item
231+ listView.visibleTilesScaleFactor)
232+ readonly property real itemScale: CarouselJS.getItemScale(distance,
233+ listView.continuousIndex,
235+ listView.visibleTilesScaleFactor)
236+ readonly property real translationX: CarouselJS.getItemTranslation(index,
237+ listView.selectedIndex,
240+ itemTranslationScale,
241+ listView.maximumItemTranslation)
243+ width: listView.tileWidth
244+ height: listView.tileHeight
245+ scale: itemScale * explicitScaleFactor
246+ sourceComponent: itemComponent
247+ z: cachedTiles - Math.abs(index - listView.selectedIndex)
172 transform: Translate {
249 transform: Translate {
173− x: CarouselJS.getViewTranslation(flickable.contentX,
250+ x: listView.viewTranslation + translationX * listView.scaleFactor
174− flickable.tileWidth,
175− flickable.gapToMiddlePhase,
176− flickable.gapToEndPhase,
177− flickable.translationXViewFactor)
180− Repeater {
253+ Behavior on explicitScaleFactor {
181− id: repeater
254+ SequentialAnimation {
183− model: carousel.model
256+ script: if (!explicitScale)
184−
257+ explicitlyScaled = false
186− property bool explicitlyScaled: explicitScaleFactor == carousel.selectedItemScaleFactor
187− property real explicitScaleFactor: explicitScale ? carousel.selectedItemScaleFactor : 1.0
188− readonly property bool explicitScale: (!flickable.moving ||
189− flickable.contentX <= 0 ||
190− flickable.contentX >= flickable.contentWidth - flickable.width) &&
191− flickable.newContentX < 0 &&
192− index === view.selectedIndex
193− readonly property real cachedTiles: flickable.realPathItemCount + carousel.cacheBuffer / flickable.tileWidth
194− readonly property real distance: view.continuousIndex - index
195− readonly property real itemTranslationScale: CarouselJS.getItemScale(0.5,
196− (index + 0.5), // good approximation of scale while changing selected item
198− flickable.visibleTilesScaleFactor)
199− readonly property real itemScale: CarouselJS.getItemScale(distance,
200− view.continuousIndex,
202− flickable.visibleTilesScaleFactor)
203− readonly property real translationFactor: (flickable.tileWidth * 3) / flickable.scaleFactor
204− readonly property real translationX: index === view.selectedIndex ? 0 :
205− CarouselJS.getItemTranslation(distance,
207− itemTranslationScale,
210− width: flickable.tileWidth
211− height: flickable.tileHeight
212− scale: itemScale * explicitScaleFactor
213− opacity: scale > 0.02 ? 1 : 0
214− sourceComponent: z > 0 ? itemComponent : undefined
215− z: cachedTiles - Math.abs(index - view.selectedIndex)
217− transform: Translate {
218− x: translationX * flickable.scaleFactor
221− Behavior on explicitScaleFactor {
222− SequentialAnimation {
224− script: if (!explicitScale)
225− explicitlyScaled = false
227− NumberAnimation {
259+ NumberAnimation {
228− duration: explicitScaleFactor === 1.0 ? 250 : 150
260+ duration: explicitScaleFactor === 1.0 ? 250 : 150
229− easing.type: Easing.InOutQuad
261+ easing.type: Easing.InOutQuad
232− script: if (explicitScale)
233− explicitlyScaled = true
238− onLoaded: {
264+ script: if (explicitScale)
239− item.explicitlyScaled = Qt.binding(function() { return explicitlyScaled; })
265+ explicitlyScaled = true
240− item.model = Qt.binding(function() { return model; })
243− MouseArea {
270+ onLoaded: {
244− id: mouseArea
271+ item.explicitlyScaled = Qt.binding(function() { return explicitlyScaled; })
272+ item.model = Qt.binding(function() { return model; })
246− anchors.fill: parent
275+ MouseArea {
278+ anchors.fill: parent
248− onClicked: flickable.itemClicked(index, item)
280+ onClicked: listView.itemClicked(index, item)