on xenial help sign in
merge with launcher-new-folding
commit7527d5e156d35d9925a3cfe3b13c9b5bb9024022 authorMichael Zanetti <michael.zanetti@canonical.com> dateWed 26 Jun 2013, 12:01 (13 years ago) parent47b308db473918
4 files changed+310−91
Launcher/LauncherDelegate.qml+202−39 Launcher/LauncherPanel.qml+106−52 Launcher/graphics/home@18.png plugins/Unity/CMakeLists.txt+2
Launcher/LauncherDelegate.qml+202−39
@@ -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}
Launcher/LauncherPanel.qml+106−52
@@ -17,6 +17,7 @@
1717import QtQuick 2.0
1818import Ubuntu.Components 0.1
1919import Unity 0.1
20+import "../Components/ListItems"
2021
2122Item {
2223 id: root
@@ -26,7 +27,7 @@ Item {
2627 property var model
2728 property bool inverted: true
2829 property bool dragging: false
29− property bool moving: launcherFlickable.moving
30+ property bool moving: launcherListView.moving || launcherListView.flicking
3031 property int dragPosition: 0
3132 property int highlightIndex: -1
3233
@@ -34,23 +35,23 @@ Item {
3435 signal dashItemSelected(int index)
3536
3637 onDragPositionChanged: {
37− var effectiveDragPosition = root.inverted ? launcherFlickable.height - dragPosition : dragPosition - mainColumn.anchors.margins
38+ var effectiveDragPosition = root.inverted ? launcherListView.height - dragPosition : dragPosition - mainColumn.anchors.margins
3839
39− var hiddenContentHeight = launcherFlickable.contentHeight - launcherFlickable.height
40+ var hiddenContentHeight = launcherListView.contentHeight - launcherListView.height
4041 // Shortening scrollable height because the first/last item needs to be fully expanded before reaching the top/bottom
41− var scrollableHeight = launcherFlickable.height - (launcherFlickable.itemSize + launcherColumn.spacing) *2
42+ var scrollableHeight = launcherListView.height - (launcherListView.itemSize + launcherColumn.spacing) *2
4243 // As we shortened the scrollableHeight, lets move everything down by the itemSize
43− var shortenedEffectiveDragPosition = effectiveDragPosition - launcherFlickable.itemSize - launcherColumn.spacing
44+ var shortenedEffectiveDragPosition = effectiveDragPosition - launcherListView.itemSize - launcherColumn.spacing
4445 var newContentY = shortenedEffectiveDragPosition * hiddenContentHeight / scrollableHeight
4546
4647 // limit top/bottom to prevent overshooting
47− launcherFlickable.contentY = Math.min(hiddenContentHeight, Math.max(0, newContentY));
48+ launcherListView.contentY = Math.min(hiddenContentHeight, Math.max(0, newContentY));
4849
4950 // Now calculate the current index:
5051 // > the current mouse position + the hidden/scolled content on top is the mouse position in the averall view
5152 // > adjust that removing all the margins
5253 // > divide by itemSize to get index
53− highlightIndex = (effectiveDragPosition + launcherFlickable.contentY - mainColumn.anchors.margins*3 - launcherColumn.spacing/2) / (launcherFlickable.itemSize + launcherColumn.spacing)
54+ highlightIndex = (effectiveDragPosition + launcherListView.contentY - mainColumn.anchors.margins*3 - launcherColumn.spacing/2) / (launcherListView.itemSize + launcherColumn.spacing)
5455 }
5556
5657 BorderImage {
@@ -61,58 +62,111 @@ Item {
6162
6263 Column {
6364 id: mainColumn
64− anchors.fill: parent
65− anchors.margins: units.gu(1)
66− spacing: units.gu(1)
65+ anchors {
66+ fill: parent
67+ topMargin: units.gu(0.5)
68+ bottomMargin: units.gu(1)
69+ leftMargin: units.gu(0.5)
70+ rightMargin: units.gu(0.5)
71+ }
72+ spacing: units.gu(0.5)
6773
68− LauncherDelegate {
74+ MouseArea {
6975 id: dashItem
70− objectName: "dashItem"
71− width: launcherFlickable.itemSize
72− height: launcherFlickable.itemSize
73− anchors.horizontalCenter: parent.horizontalCenter
74− iconName: "dash"
76+ width: parent.width
77+ height: units.gu(6.5)
7578 onClicked: root.dashItemSelected(0)
79+ z: 1
80+ Image {
81+ objectName: "dashItem"
82+ width: units.gu(5.5)
83+ height: width
84+ anchors.centerIn: parent
85+ source: "graphics/home.png"
86+ }
7687 }
77− Flickable {
78− id: launcherFlickable
88+ ThinDivider {
89+ anchors {
90+ left: parent.left
91+ right: parent.right
92+ margins: -mainColumn.anchors.leftMargin
93+ }
94+ rotation: root.rotation
95+ }
96+
97+ Item {
7998 anchors.left: parent.left
8099 anchors.right: parent.right
81− height: parent.height - dashItem.height - parent.spacing
82− contentHeight: launcherColumn.height
83−
84− property int itemSize: width
85−
86− Column {
87− id: launcherColumn
88− width: parent.width
89− spacing: units.gu(1)
90− anchors.horizontalCenter: parent.horizontalCenter
91−
92− Repeater {
93− id: iconRepeater
94− model: root.model
95−
96− LauncherDelegate {
97− id: launcherDelegate
98− objectName: "launcherDelegate" + index
99− width: launcherFlickable.itemSize
100− height: launcherFlickable.itemSize
101− iconName: model.icon
102− inverted: root.inverted
103− highlighted: root.dragging && index === root.highlightIndex
104− z: -Math.abs(offset)
105− state: "docked"
106−
107− maxAngle: 60
108−
109− itemsBeforeThis: index
110− itemsAfterThis: iconRepeater.count - (index+1)
111−
112− onClicked: {
113− root.applicationSelected(launcherModel.get(index).desktopFile);
114− }
100+ height: parent.height - dashItem.height - parent.spacing*2
101+ ListView {
102+ id: launcherListView
103+ anchors.fill: parent
104+ anchors.topMargin: -itemSize
105+ anchors.bottomMargin: -itemSize
106+ topMargin: itemSize
107+ bottomMargin: itemSize
108+ height: parent.height - dashItem.height - parent.spacing*2
109+ model: root.model
110+ cacheBuffer: itemSize * 3
111+ snapMode: ListView.SnapToItem
112+ highlightRangeMode: ListView.ApplyRange
113+ preferredHighlightBegin: (height - itemSize) / 2
114+ preferredHighlightEnd: (height + itemSize) / 2
115+
116+ // The height of the area where icons start getting folded
117+ property int foldingAreaHeight: itemSize * 0.75
118+ property int itemSize: width
119+
120+ Component.onCompleted: {
121+ // This is needed because snapping would partially fold
122+ // the first item on initialisation. This can only be
123+ // overridden by mouse interactions - which is what flick()
124+ // simulates. Setting contentY to 0 would not work because
125+ // of this.
126+ flick(0, units.gu(20))
127+ }
128+
129+ delegate: LauncherDelegate {
130+ id: launcherDelegate
131+ objectName: "launcherDelegate" + index
132+ width: launcherListView.itemSize
133+ height: launcherListView.itemSize
134+ iconName: model.icon
135+ inverted: root.inverted
136+ highlighted: root.dragging && index === root.highlightIndex
137+ z: -Math.abs(offset)
138+ state: "docked"
139+ maxAngle: 60
140+
141+ onClicked: {
142+ root.applicationSelected(launcherModel.get(index).desktopFile);
143+ }
144+ }
145+
146+ MouseArea {
147+ id: topFoldingArea
148+ anchors {
149+ left: parent.left
150+ right: parent.right
151+ top: parent.top
152+ topMargin: launcherListView.topMargin
153+ }
154+ height: launcherListView.itemSize
155+ enabled: launcherListView.contentY > -launcherListView.topMargin
156+ onClicked: launcherListView.flick(0, units.gu(40))
157+ }
158+
159+ MouseArea {
160+ id: bottomFoldingArea
161+ anchors {
162+ left: parent.left
163+ right: parent.right
164+ bottom: parent.bottom
165+ bottomMargin: launcherListView.bottomMargin
115166 }
167+ height: launcherListView.itemSize
168+ enabled: launcherListView.contentHeight - launcherListView.height - launcherListView.contentY > -launcherListView.bottomMargin
169+ onClicked: launcherListView.flick(0, -units.gu(40))
116170 }
117171 }
118172 }
Launcher/graphics/home@18.pngnew file

Binary file not shown.

plugins/Unity/CMakeLists.txt+2
@@ -9,6 +9,8 @@ include_directories(
99 ${QTDEE_INCLUDE_DIRS}
1010 ${UNITYCORE_INCLUDE_DIRS}
1111 ${Qt5DBus_INCLUDE_DIRS}
12+#TODO: remove this once unity-api is released to distro
13+ /home/mzanetti/Development/unity-api/include
1214)
1315
1416set(QMLPLUGIN_SRC