2
* Copyright (C) 2013 Canonical, Ltd.
4
* This program is free software; you can redistribute it and/or modify
5
* it under the terms of the GNU General Public License as published by
6
* the Free Software Foundation; version 3.
8
* This program is distributed in the hope that it will be useful,
9
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
* GNU General Public License for more details.
13
* You should have received a copy of the GNU General Public License
14
* along with this program. If not, see <http://www.gnu.org/licenses/>.
18
import Ubuntu.Components 0.1
19
import "carousel.js" as CarouselJS
24
property Component itemComponent
26
property alias minimumTileWidth: flickable.minimumTileWidth
27
property alias pathItemCount: flickable.pathItemCount
28
property alias tileAspectRatio: flickable.tileAspectRatio
29
property int cacheBuffer: 0
30
property real selectedItemScaleFactor: 1.1
32
signal clicked(int index, var delegateItem, real itemY)
34
implicitHeight: flickable.tileHeight * selectedItemScaleFactor
36
/* TODO: evaluate if the component could be more efficient with a ListView,
37
using this technique https://bugreports.qt-project.org/browse/QTBUG-29173 */
42
property real minimumTileWidth: 0
43
property real newContentX: -1
44
property real pathItemCount: referenceWidth / referenceTileWidth
45
property real tileAspectRatio: 1
47
/* The positioning and scaling of the items in the carousel is based on the variable
48
'continuousIndex', a continuous real variable between [0, 'carousel.model.count'],
49
roughly representing the index of the item that is prioritised over the others.
50
'continuousIndex' is not linear, but is weighted depending on if it is close
51
to the beginning of the content (beginning phase), in the middle (middle phase)
52
or at the end (end phase).
53
Each tile is scaled and transformed in proportion to the difference between
54
its own index and continuousIndex.
55
To efficiently calculate continuousIndex, we have these values:
56
- 'gapToMiddlePhase' gap in pixels between beginning and middle phase
57
- 'gapToEndPhase' gap in pixels between middle and end phase
58
- 'kGapEnd' constant used to calculate 'continuousIndex' in end phase
59
- 'kMiddleIndex' constant used to calculate 'continuousIndex' in middle phase
60
- 'kXBeginningEnd' constant used to calculate 'continuousIndex' in beginning and end phase. */
62
readonly property real gapToMiddlePhase: Math.min(width / 2 - tileWidth / 2, (contentWidth - width) / 2)
63
readonly property real gapToEndPhase: contentWidth - width - gapToMiddlePhase
64
readonly property real kGapEnd: kMiddleIndex * (1 - gapToEndPhase / gapToMiddlePhase)
65
readonly property real kMiddleIndex: (width / 2) / tileWidth - 0.5
66
readonly property real kXBeginningEnd: 1 / tileWidth + kMiddleIndex / gapToMiddlePhase
67
readonly property real realPathItemCount: Math.min(width / tileWidth, pathItemCount)
68
readonly property real referenceGapToMiddlePhase: width / 2 - tileWidth / 2
69
readonly property real referencePathItemCount: referenceWidth / referenceTileWidth
70
readonly property real referenceWidth: 848
71
readonly property real referenceTileWidth: 175
72
readonly property real scaleFactor: tileWidth / referenceTileWidth
73
readonly property real tileWidth: Math.max(width / pathItemCount, minimumTileWidth)
74
readonly property real tileHeight: tileWidth / tileAspectRatio
75
readonly property real translationXViewFactor: 0.2 * (referenceGapToMiddlePhase / gapToMiddlePhase)
76
readonly property real verticalMargin: (parent.height - tileHeight) / 2
77
readonly property real visibleTilesScaleFactor: realPathItemCount / referencePathItemCount
81
topMargin: verticalMargin
82
bottomMargin: verticalMargin
84
contentWidth: view.width
86
boundsBehavior: Flickable.StopAtBounds
87
flickDeceleration: Math.max(1500 * Math.pow(width / referenceWidth, 1.5), 1500) // 1500 is platform default
88
maximumFlickVelocity: Math.max(2500 * Math.pow(width / referenceWidth, 1.5), 2500) // 2500 is platform default
90
function itemClicked(index, delegateItem) {
91
var x = CarouselJS.getXFromContinuousIndex(index,
98
if (Math.abs(x - contentX) < 1) {
99
/* We're clicking the selected item and
100
we're in the neighbourhood of radius 1 pixel from it.
101
Let's emit the clicked signal. */
102
carousel.clicked(index, delegateItem, delegateItem.y)
107
newContentXAnimation.stop()
110
newContentXAnimation.start()
115
newContentXAnimation.stop()
119
if (contentX > 0 && contentX < contentWidth - width)
120
stepAnimation.start()
128
from: flickable.contentX
129
to: CarouselJS.getXFromContinuousIndex(view.selectedIndex,
131
flickable.contentWidth,
133
flickable.gapToMiddlePhase,
134
flickable.gapToEndPhase)
137
easing.type: Easing.InOutQuad
140
SequentialAnimation {
141
id: newContentXAnimation
146
from: flickable.contentX
147
to: flickable.newContentX
149
easing.type: Easing.InOutQuad
152
script: flickable.newContentX = -1
159
readonly property int selectedIndex: Math.round(continuousIndex)
160
readonly property real continuousIndex: CarouselJS.getContinuousIndex(flickable.contentX,
162
flickable.gapToMiddlePhase,
163
flickable.gapToEndPhase,
165
flickable.kMiddleIndex,
166
flickable.kXBeginningEnd)
168
height: parent.height
169
anchors.verticalCenter: parent.verticalCenter
171
transform: Translate {
172
x: CarouselJS.getViewTranslation(flickable.contentX,
174
flickable.gapToMiddlePhase,
175
flickable.gapToEndPhase,
176
flickable.translationXViewFactor)
182
model: carousel.model
185
property bool explicitlyScaled: explicitScaleFactor == carousel.selectedItemScaleFactor
186
property real explicitScaleFactor: explicitScale ? carousel.selectedItemScaleFactor : 1.0
187
readonly property bool explicitScale: (!flickable.moving ||
188
flickable.contentX <= 0 ||
189
flickable.contentX >= flickable.contentWidth - flickable.width) &&
190
flickable.newContentX < 0 &&
191
index === view.selectedIndex
192
readonly property real cachedTiles: flickable.realPathItemCount + carousel.cacheBuffer / flickable.tileWidth
193
readonly property real distance: view.continuousIndex - index
194
readonly property real itemTranslationScale: CarouselJS.getItemScale(0.5,
195
(index + 0.5), // good approximation of scale while changing selected item
197
flickable.visibleTilesScaleFactor)
198
readonly property real itemScale: CarouselJS.getItemScale(distance,
199
view.continuousIndex,
201
flickable.visibleTilesScaleFactor)
202
readonly property real translationFactor: (flickable.tileWidth * 3) / flickable.scaleFactor
203
readonly property real translationX: index === view.selectedIndex ? 0 :
204
CarouselJS.getItemTranslation(distance,
206
itemTranslationScale,
209
width: flickable.tileWidth
210
height: flickable.tileHeight
211
scale: itemScale * explicitScaleFactor
212
opacity: scale > 0.02 ? 1 : 0
213
sourceComponent: z > 0 ? itemComponent : undefined
214
z: cachedTiles - Math.abs(index - view.selectedIndex)
216
transform: Translate {
217
x: translationX * flickable.scaleFactor
220
Behavior on explicitScaleFactor {
221
SequentialAnimation {
223
script: if (!explicitScale)
224
explicitlyScaled = false
227
duration: explicitScaleFactor === 1.0 ? 250 : 150
228
easing.type: Easing.InOutQuad
231
script: if (explicitScale)
232
explicitlyScaled = true
238
item.explicitlyScaled = Qt.binding(function() { return explicitlyScaled; })
239
item.model = Qt.binding(function() { return model; })
247
onClicked: flickable.itemClicked(index, item)