@@ -25,6 +25,8 @@ Item {
2525 property real angle: 0
2626 property bool highlighted: false
2727 property real offset: 0
28+ property alias brightness: transformEffect.brightness
29+ property real itemOpacity: 1
2830
2931 property real maxAngle: 0
3032 property bool inverted: false
@@ -32,58 +34,138 @@ Item {
3234 readonly property int effectiveHeight: Math.cos(angle * Math.PI / 180) * height
3335 readonly property real foldedHeight: Math.cos(maxAngle * Math.PI / 180) * height
3436
35− property int itemsBeforeThis: 0
36− property int itemsAfterThis: 0
37−
3837 property bool dragging:false
3938
4039 signal clicked()
4140 signal longtap()
4241 signal released()
4342
44− UbuntuShape {
43+ Item {
4544 id: iconItem
46− color: Qt.rgba(0, 0, 1, 0.5)
4745 width: parent.width
4846 height: parent.height
4947 anchors.centerIn: parent
50− anchors.verticalCenterOffset: root.offset + (height - root.effectiveHeight)/2 * (angle < 0 ? -1 : 1)
51− rotation: root.inverted ? 180 : 0
52− radius: "medium"
5348
54− transform: Rotation {
55− axis { x: 1; y: 0; z: 0 }
56− origin { x: iconItem.width / 2; y: iconItem.height / 2; z: 0 }
57− angle: root.angle
58− }
49+ UbuntuShape {
50+ width: parent.width - units.gu(1)
51+ height: parent.height - units.gu(1)
52+ anchors.centerIn: parent
53+ radius: "medium"
5954
60− image: Image {
61− source: "../graphics/applicationIcons/" + root.iconName + ".png"
62− }
55+ image: Image {
56+ id: iconImage
57+ sourceSize.width: parent.width
58+ sourceSize.height: parent.height
59+ source: "../graphics/applicationIcons/" + root.iconName + ".png"
60+ }
6361
64− MouseArea {
65− id: mouseArea
66− anchors.fill: parent
67− onClicked: root.clicked()
68− onCanceled: root.released()
69− preventStealing: false
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
7069
71− onPressAndHold: {
72− root.state = "moving"
73− }
74− onReleased: {
75− root.state = "docked"
70+ onPressAndHold: {
71+ root.state = "moving"
72+ }
73+ onReleased: {
74+ root.state = "docked"
75+ }
7676 }
7777 }
78−
7978 BorderImage {
8079 id: overlayHighlight
8180 anchors.centerIn: iconItem
8281 rotation: inverted ? 180 : 0
83− source: root.highlighted || mouseArea.pressed ? "graphics/selected.sci" : "graphics/non-selected.sci"
84− width: iconItem.width + units.gu(1.5)
82+ source: isSelected ? "graphics/selected.sci" : "graphics/non-selected.sci"
83+ width: root.width + units.gu(0.5)
8584 height: width
85+ property bool isSelected: root.highlighted || mouseArea.pressed
86+ onIsSelectedChanged: shaderEffectSource.scheduleUpdate();
87+ }
88+ }
89+
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
100+
101+
102+ property variant source: ShaderEffectSource {
103+ id: shaderEffectSource
104+ sourceItem: iconItem
105+ hideSource: true
106+ live: false
86107 }
108+
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+ ]
137+
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+ }
153+
154+ QtObject {
155+ id: priv
156+
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
164+
165+ property real distanceFromEdge: Math.abs(distanceFromBottomEdge) < Math.abs(distanceFromTopEdge) ? distanceFromBottomEdge : distanceFromTopEdge
166+ property real orientationFlag: Math.abs(distanceFromBottomEdge) < Math.abs(distanceFromTopEdge) ? -1 : 1
167+
168+ property real overlapWithFoldingArea: listView.foldingAreaHeight - distanceFromEdge
87169 }
88170
89171 states: [
@@ -91,15 +173,98 @@ Item {
91173 name: "docked"
92174 PropertyChanges {
93175 target: root
94− offset: if (launcherFlickable.contentY > itemsBeforeThis * (foldedHeight + launcherColumn.spacing*2)) {
95− return launcherFlickable.contentY - (index * (foldedHeight + launcherColumn.spacing*2));
96− } else if (y + height - launcherFlickable.contentY > launcherFlickable.height - (itemsAfterThis*(foldedHeight - launcherColumn.spacing))) {
97− return launcherFlickable.height - (y+height) + launcherFlickable.contentY - (itemsAfterThis*(foldedHeight - launcherColumn.spacing));
98− } else {
99− return 0;
176+
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;
100184 }
101− angle: -Math.min(Math.max(offset * maxAngle / foldedHeight, -maxAngle), maxAngle)
185+ return 0;
186+ }
187+
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+ }
192+
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+ }
199+
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+ }
210+
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+ }
102216
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+ }
224+
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+ }
234+
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+ }
240+
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+ }
248+
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+ }
261+
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+ }
103268 }
104269 },
105270
@@ -121,7 +286,5 @@ Item {
121286 drag.target: root
122287 }
123288 }
124−
125289 ]
126−
127290}