on 4ecd038 help sign in
unity8/Launcher/LauncherDelegate.qml
290 lines 12 kB history raw
1/*
2 * Copyright (C) 2013 Canonical, Ltd.
3 *
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.
7 *
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.
12 *
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/>.
15 */
17import QtQuick 2.0
18import Ubuntu.Components 0.1
20Item {
21 id: root
23 property string iconName
25 property real angle: 0
26 property bool highlighted: false
27 property real offset: 0
28 property alias brightness: transformEffect.brightness
29 property real itemOpacity: 1
31 property real maxAngle: 0
32 property bool inverted: false
34 readonly property int effectiveHeight: Math.cos(angle * Math.PI / 180) * height
35 readonly property real foldedHeight: Math.cos(maxAngle * Math.PI / 180) * height
37 property bool dragging:false
39 signal clicked()
40 signal longtap()
41 signal released()
43 Item {
44 id: iconItem
45 width: parent.width
46 height: parent.height
47 anchors.centerIn: parent
49 UbuntuShape {
50 id: iconShape
51 anchors.fill: parent
52 anchors.margins: units.gu(0.5)
53 radius: "medium"
55 image: Image {
56 id: iconImage
57 sourceSize.width: iconShape.width
58 sourceSize.height: iconShape.height
59 source: "../graphics/applicationIcons/" + root.iconName + ".png"
60 }
62 MouseArea {
63 id: mouseArea
64 anchors.fill: parent
65 onClicked: root.clicked()
66 onCanceled: root.released()
67 preventStealing: false
68 enabled: root.angle < 5
70 onPressAndHold: {
71 root.state = "moving"
72 }
73 onReleased: {
74 root.state = "docked"
75 }
76 }
77 }
78 BorderImage {
79 id: overlayHighlight
80 anchors.centerIn: iconItem
81 rotation: inverted ? 180 : 0
82 source: isSelected ? "graphics/selected.sci" : "graphics/non-selected.sci"
83 width: root.width + units.gu(0.5)
84 height: width
85 property bool isSelected: root.highlighted || mouseArea.pressed
86 onIsSelectedChanged: shaderEffectSource.scheduleUpdate();
87 }
88 }
90 ShaderEffect {
91 id: transformEffect
92 anchors.centerIn: parent
93 anchors.verticalCenterOffset: root.offset
94 width: parent.width
95 height: parent.height
96 property real itemOpacity: root.itemOpacity
97 property real brightness: 0
98 property real angle: root.angle
99 rotation: root.inverted ? 180 : 0
102 property variant source: ShaderEffectSource {
103 id: shaderEffectSource
104 sourceItem: iconItem
105 hideSource: true
106 live: false
107 }
109 transform: [
110 // Rotating 3 times at top/bottom because that increases the perspective.
111 // This is a hack, but as QML does not support real 3D coordinates
112 // getting a higher perspective can only be done by a hack. This is the most
113 // readable/understandable one I could come up with.
114 Rotation {
115 axis { x: 1; y: 0; z: 0 }
116 origin { x: iconItem.width / 2; y: angle > 0 ? 0 : iconItem.height; z: 0 }
117 angle: root.angle * 0.7
118 },
119 Rotation {
120 axis { x: 1; y: 0; z: 0 }
121 origin { x: iconItem.width / 2; y: angle > 0 ? 0 : iconItem.height; z: 0 }
122 angle: root.angle * 0.7
123 },
124 Rotation {
125 axis { x: 1; y: 0; z: 0 }
126 origin { x: iconItem.width / 2; y: angle > 0 ? 0 : iconItem.height; z: 0 }
127 angle: root.angle * 0.7
128 },
129 // Because rotating it 3 times moves it more to the front/back, i.e. it gets
130 // bigger/smaller and we need a scale to compensate that again.
131 Scale {
132 xScale: 1 - (Math.abs(angle) / 500)
133 yScale: 1 - (Math.abs(angle) / 500)
134 origin { x: iconItem.width / 2; y: iconItem.height / 2}
135 }
136 ]
138 // Using a fragment shader instead of QML's opacity and BrightnessContrast
139 // to be able to do both in one step which gives quite some better performance
140 fragmentShader: "
141 varying highp vec2 qt_TexCoord0;
142 uniform sampler2D source;
143 uniform lowp float brightness;
144 uniform lowp float itemOpacity;
145 void main(void)
146 {
147 highp vec4 sourceColor = texture2D(source, qt_TexCoord0);
148 sourceColor.rgb = mix(sourceColor.rgb, vec3(step(0.0, brightness)), abs(brightness));
149 sourceColor *= itemOpacity;
150 gl_FragColor = sourceColor;
151 }"
152 }
154 QtObject {
155 id: priv
157 property ListView listView: root.ListView.view
158 property real totalUnfoldedHeight: listView.itemSize + listView.spacing
159 property real totalEffectiveHeight: effectiveHeight + listView.spacing
160 property real effectiveContentY: listView.contentY - listView.originY
161 property real effectiveY: y - listView.originY
162 property real distanceFromTopEdge: -(effectiveContentY + listView.topMargin - index*totalUnfoldedHeight)
163 property real distanceFromBottomEdge: listView.height - listView.bottomMargin - (effectiveY+height) + effectiveContentY
165 property real distanceFromEdge: Math.abs(distanceFromBottomEdge) < Math.abs(distanceFromTopEdge) ? distanceFromBottomEdge : distanceFromTopEdge
166 property real orientationFlag: Math.abs(distanceFromBottomEdge) < Math.abs(distanceFromTopEdge) ? -1 : 1
168 property real overlapWithFoldingArea: listView.foldingAreaHeight - distanceFromEdge
169 }
171 states: [
172 State {
173 name: "docked"
174 PropertyChanges {
175 target: root
177 // This is the offset that keeps the items inside the panel
178 offset: {
179 // First/last items are special
180 if (index == 0 || index == priv.listView.count-1) {
181 // Just keep them bound to the edges in case they're outside of the visible area
182 if (priv.distanceFromEdge < 0) {
183 return (-priv.distanceFromEdge - (height - effectiveHeight)) * priv.orientationFlag;
184 }
185 return 0;
186 }
188 // Are we already completely outside the flickable? Stop the icon here.
189 if (priv.distanceFromEdge < -priv.totalUnfoldedHeight) {
190 return (-priv.distanceFromEdge - (root.height - effectiveHeight)) * priv.orientationFlag;
191 }
193 // We're touching the edge, move slower than the actual flicking speed.
194 if (priv.distanceFromEdge < 0) {
195 return (Math.abs(priv.distanceFromEdge) * priv.totalEffectiveHeight / priv.totalUnfoldedHeight) * priv.orientationFlag
196 }
197 return 0;
198 }
200 // The angle used for rotating
201 angle: {
202 // First/last items are special
203 if (index == 0 || index == priv.listView.count-1) {
204 if (priv.distanceFromEdge < 0) {
205 // proportion equation: distanceFromTopEdge : angle = totalUnfoldedHeight/2 : maxAngle
206 return Math.max(-maxAngle, priv.distanceFromEdge * maxAngle / (priv.listView.foldingAreaHeight)) * priv.orientationFlag
207 }
208 return 0; // Don't fold first/last item as long as inside the view
209 }
211 // Are we in the already completely outside the flickable? Fold for the last 5 degrees
212 if (priv.distanceFromEdge < 0) {
213 // proportion equation: -distanceFromTopEdge : angle = totalUnfoldedHeight : 5
214 return Math.max(-maxAngle, (priv.distanceFromEdge * 5 / priv.listView.foldingAreaHeight) - (maxAngle-5)) * priv.orientationFlag
215 }
217 // We are overlapping with the folding area, fold the icon to maxAngle - 5 degrees
218 if (priv.overlapWithFoldingArea > 0) {
219 // proportion equation: overlap: totalHeight = angle : (maxAngle - 5)
220 return -priv.overlapWithFoldingArea * (maxAngle -5) / priv.listView.foldingAreaHeight * priv.orientationFlag;
221 }
222 return 0;
223 }
225 itemOpacity: {
226 // First/last items are special
227 if (index == 0 || index == priv.listView.count-1) {
228 if (priv.distanceFromEdge < 0) {
229 // Fade from 1 to 0 in the distance of 3 * foldingAreaHeight (which is when the next item reaches the edge)
230 return 1.0 - (-priv.distanceFromEdge / (priv.listView.foldingAreaHeight * 3))
231 }
232 return 1; // Don't make first/last item transparent as long as inside the view
233 }
235 // Are we already completely outside the flickable? Fade from 0.75 to 0 in 2 items height
236 if (priv.distanceFromEdge < 0) {
237 // proportion equation: -distanceFromEdge : 1-opacity = totalUnfoldedHeight : 0.75
238 return 0.75 - (-priv.distanceFromEdge * 0.75 / (priv.totalUnfoldedHeight*2))
239 }
241 // We are overlapping with the folding area, fade out to 0.75
242 if (priv.overlapWithFoldingArea > 0) {
243 // proportion equation: overlap : totalHeight = 1-opacity : 0.25
244 return 1 - (priv.overlapWithFoldingArea * 0.25 / priv.listView.foldingAreaHeight)
245 }
246 return 1;
247 }
249 brightness: {
250 // First/last items are special
251 if (index == 0 || index == priv.listView.count-1) {
252 if (priv.distanceFromEdge < 0) {
253 return -(-priv.distanceFromEdge / (priv.listView.foldingAreaHeight * 3))
254 }
255 return 0;
256 }
257 // Are we already completely outside the flickable? Fade from 0.7 to 0 in 2 items height
258 if (priv.distanceFromEdge < 0) {
259 return -0.3 - (-priv.distanceFromEdge * 0.1 / (priv.totalUnfoldedHeight*2))
260 }
262 // We are overlapping with the folding area, fade out to 0.7
263 if (priv.overlapWithFoldingArea > 0) {
264 return - (priv.overlapWithFoldingArea * 0.3 / priv.listView.foldingAreaHeight)
265 }
266 return 0;
267 }
268 }
269 },
271 State {
272 name: "moving"
273 PropertyChanges {
274 target: launcherDelegate;
275 offset: 0
276 angle: 0
277 }
278 PropertyChanges {
279 target: root
280 highlighted: true
281 dragging: true
282 }
283 PropertyChanges {
284 target: mouseArea
285 preventStealing: true
286 drag.target: root
287 }
288 }
289 ]