Merge lp:~mzanetti/unity8/dnd-and-quicklists into lp:unity8

Proposed by Michael Zanetti
Status: Merged
Approved by: Michał Sawicz
Approved revision: 143
Merged at revision: 214
Proposed branch: lp:~mzanetti/unity8/dnd-and-quicklists
Merge into: lp:unity8
Diff against target: 1268 lines (+694/-364)
13 files modified
Launcher/FoldingLauncherDelegate.qml (+94/-250)
Launcher/Launcher.qml (+2/-2)
Launcher/LauncherDelegate.qml (+137/-0)
Launcher/LauncherPanel.qml (+330/-101)
Launcher/graphics/launcher_bg@18.sci (+0/-5)
Launcher/graphics/launcher_bg@30.sci (+5/-0)
plugins/Unity/Launcher/backend/launcherbackend.cpp (+1/-1)
plugins/Unity/Launcher/launcheritem.cpp (+8/-1)
plugins/Unity/Launcher/launchermodel.cpp (+34/-4)
plugins/Unity/Launcher/plugin.cpp (+2/-0)
plugins/Unity/Launcher/quicklistmodel.cpp (+11/-0)
plugins/Unity/Launcher/quicklistmodel.h (+10/-0)
tests/qmltests/Launcher/tst_Launcher.qml (+60/-0)
To merge this branch: bzr merge lp:~mzanetti/unity8/dnd-and-quicklists
Reviewer Review Type Date Requested Status
Michał Sawicz Approve
PS Jenkins bot (community) continuous-integration Approve
Review via email: mp+176653@code.launchpad.net

Commit message

Add Drag'n'drop support to Launcher

As dragging an item pins it to the launcher this also contains initial quicklist and pinning support in the plugin part.

Description of the change

Quicklist support in the UI has been temporarily dropped as the Popover is nowhere near ready for that use case yet. So this one has Quicklist support only in the C++ part.

To post a comment you must log in.
122. By Michael Zanetti

remove duplicate dummy items again

Revision history for this message
Michał Sawicz (saviq) wrote :

Outputs:

state changed to expanded
state changed to

There's some jerkiness when going back from expanded state, the folded items jump into place - especially if you drop the item in a place where it will become folded. Either we should scroll more (so that the item isn't folded after dropping) or maybe delay the folding... Or something...

review: Needs Information (functional)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Michael Zanetti (mzanetti) wrote :

> There's some jerkiness when going back from expanded state, the folded items
> jump into place - especially if you drop the item in a place where it will
> become folded. Either we should scroll more (so that the item isn't folded
> after dropping) or maybe delay the folding... Or something...

This is the same bug as the initial folded item. Should be fixed once we manage to patch the ListView in upstream Qt.

123. By Michael Zanetti

remove debug print

Revision history for this message
Michael Zanetti (mzanetti) wrote :

> Outputs:
>
> state changed to expanded
> state changed to

removed

Revision history for this message
Michael Zanetti (mzanetti) wrote :

> > There's some jerkiness when going back from expanded state, the folded items
> > jump into place - especially if you drop the item in a place where it will
> > become folded. Either we should scroll more (so that the item isn't folded
> > after dropping) or maybe delay the folding... Or something...
>
>
> This is the same bug as the initial folded item. Should be fixed once we
> manage to patch the ListView in upstream Qt.

Right... there's another issue. By the time the transition from "expanded" to "" is triggered, the item is still closer to the center, which means less folding. Apparently the target angle gets calculated for this position, e.g. 18°. Now, while it folds to that target angle it gets also moved towards the end because of the dropped item is expanding. Being towards closer to the edge means that now it would need to fold to 53° instead of only 18° though. Once the folding transition to 18° is done, it updates again to the new target angle which causes the jump. Need to figure a solution.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Michał Sawicz (saviq) wrote :
Download full text (3.2 KiB)

I wonder if it'd make sense to "enable: angle != 0" the ShaderEffect?

=====

Ultimately I'd like to see a shader-based replacement for the three rotations...

=====

Ideas of better names for LauncherDelegate and LauncherListDelegate? Like LauncherItem and LauncherDelegate, maybe?

=====

 604 + Item {

Do we really need this?

=====

 658 + layoutDirection: root.inverted ? Qt.RightToLeft : Qt.LeftToRight

Hum?

=====

 662 + // destructed. Note that if the move() operation when dragging switches

destructed → destroyed

=====

Can we protect ourselves somehow from the "breaks badly"? Like limit the dragging distance?

=====

 674 + NumberAnimation { properties: "x,y"; duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing }

 743 + NumberAnimation { properties: "angle,offset"; duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing }

 749 + NumberAnimation { properties: "angle,offset"; duration: UbuntuAnimation.BriskDuration; easing: UbuntuAnimation.StandardEasing }

 754 + NumberAnimation { properties: "height"; duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing }

 760 + NumberAnimation { properties: "height"; duration: UbuntuAnimation.BriskDuration; easing: UbuntuAnimation.StandardEasing }

Use UbuntuNumberAnimation {} here.

=====

You need a State { name: "" } with the default values, otherwise the *current* values (e.g. in the middle of a transition) will be assumed as the default ones.

=====
 695 + opacity: parent.dragging ? 1 : 0
 696 + Behavior on opacity {
 697 + NumberAnimation { duration: UbuntuAnimation.FastDuration }
 698 + }

Since there are states already, maybe move that there? UbuntuNumberAnimation could be used here, too, although I think someone said we shouldn't use the easing for opacity?

=====

 729 + }
 730 +
 731 + ]

Drop newline.

=====

 767 + anchors.fill: parent
 768 + anchors.topMargin: launcherListView.topMargin
 769 + anchors.bottomMargin: launcherListView.bottomMargin

Try to remember and group those.

=====

 778 + var realContentY = launcherListView.contentY + launcherListView.topMargin

 785 + var realContentY = launcherListView.contentY + launcherListView.topMargin

 873 + var realContentY = launcherListView.contentY + launcherListView.topMargin

Should take originY into account.

Maybe make it into a function to reuse? Same for realItemHeight?

=====

 790 + if (index == 0 || index == launcherListView.count -1) {

Space before -.

=====

Will dragging work fine with the launcher (not) inverted?

=====

There's quicklist-related stuff in launcheritem, care to move it to when it will actually work?

===...

Read more...

review: Needs Fixing
124. By Michael Zanetti

make folding after dragging sequential to expanding to avoid weird jumpiness

125. By Michael Zanetti

LauncherListDelegate -> FoldingLauncherDelegate

126. By Michael Zanetti

remove unneeded listview layoutdireaction property

127. By Michael Zanetti

fix comment

128. By Michael Zanetti

limit drag area size to avoid brakage when dragging far outside the window

129. By Michael Zanetti

use UbuntuNumberAnimation where appropriate

130. By Michael Zanetti

handle dropIndicator's state in the other states too

131. By Michael Zanetti

drop empty line

132. By Michael Zanetti

group anchors

133. By Michael Zanetti

use properties instead of common calculations

134. By Michael Zanetti

add whitespace

Revision history for this message
Michael Zanetti (mzanetti) wrote :
Download full text (5.1 KiB)

> I wonder if it'd make sense to "enable: angle != 0" the ShaderEffect?

Hmm... Not sure... I also use itemOpacity when it is unfolded. Would require to be very careful with distinguishing opacity and itemOpacity (which intentionally are 2 different things). The gain would be very minimal I think.

>
> =====
>
> Ultimately I'd like to see a shader-based replacement for the three
> rotations...

Sure. But somewhat out of the scope of this merge.

>
> =====
>
> Ideas of better names for LauncherDelegate and LauncherListDelegate? Like
> LauncherItem and LauncherDelegate, maybe?

renamed LauncherListDelegate to FoldingLauncherDelegate.

>
> =====
>
> 604 + Item {
>
> Do we really need this?

Yes. This does the clipping when unfolded. I need to have the MouseArea to be a child of the ListView otherwise weird mouse event stealing things will happen. Also, the dragTarget (the dragFakeItem) should be in a parent of the same size as the MouseArea or it indtroduces long calculations and nasty jumping at transitions. I tried that before.

>
> =====
>
> 658 + layoutDirection: root.inverted ? Qt.RightToLeft :
> Qt.LeftToRight
>
> Hum?

I got fed up by the rotating thing and tried if we couldn't abuse the layoutDirection for the inversion. Doesn't work unfortunately. Removed this leftover.

>
> =====
>
> 662 + // destructed. Note that if the move() operation
> when dragging switches
>
> destructed → destroyed

fixed.

>
> =====
>
> Can we protect ourselves somehow from the "breaks badly"? Like limit the
> dragging distance?

Very good point. I've limited the drag area and haven't been able to reproduce this any more.

>
> =====
>
> 674 + NumberAnimation { properties: "x,y"; duration:
> UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing }
>
> 743 + NumberAnimation { properties:
> "angle,offset"; duration: UbuntuAnimation.FastDuration; easing:
> UbuntuAnimation.StandardEasing }
>
> 749 + NumberAnimation { properties:
> "angle,offset"; duration: UbuntuAnimation.BriskDuration; easing:
> UbuntuAnimation.StandardEasing }
>
> 754 + NumberAnimation { properties: "height";
> duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing
> }
>
> 760 + NumberAnimation { properties: "height";
> duration: UbuntuAnimation.BriskDuration; easing:
> UbuntuAnimation.StandardEasing }
>
> Use UbuntuNumberAnimation {} here.

Done.

>
> =====
>
> You need a State { name: "" } with the default values, otherwise the *current*
> values (e.g. in the middle of a transition) will be assumed as the default
> ones.

The behavior you describe is pretty much what I want. When leaving one of those special states, just go back to whatever it was before.

>
> =====
> 695 + opacity: parent.dragging ? 1 : 0
> 696 + Behavior on opacity {
> 697 + NumberAnimation { duration:
> UbuntuAnimation.FastDuration }
> 698 + }
>
> ...

Read more...

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
135. By Michael Zanetti

take originY into account for progressiveScrolling

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
136. By Michael Zanetti

use new launcher background asset

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
137. By Michael Zanetti

fix relasing before dragging, improve test

138. By Michael Zanetti

invert it again

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Michał Sawicz (saviq) wrote :

I wonder if invoking dnd should be disabled on folded icons? Otherwise:
* when two items are folded and you long press the bottom one, the one on top of it will be used
* there's no unfolding animation visible when invoking d'n'd on a folded icon

Dropping at the top still results in some jumping.

Weren't "other" launcher items supposed to dim on long press? They only dim onDrag now, somewhat tricky to see that it's "now" if only the item under your finger changes.

Sometimes I end up with an empty space the size of an item that... Sometimes moves around, sometimes not. Dragging *inside* the launcher seems to be the easiest to reproduce.

I'd be OK with merging this if you say these should be fixed at a later iteration, though.

review: Needs Information
139. By Michael Zanetti

make dropping at the end of the listView more smooth

140. By Michael Zanetti

also make dropping at the second last position smoother

141. By Michael Zanetti

only apply flicking when in postDragging state

142. By Michael Zanetti

cleanup

143. By Michael Zanetti

disable dragging of items with an angle > 20 degrees
add an animation for dragging of items with an angle > 0 and < 20
remove foldingMouseAreas as they are not needed any more after all the other dnd changes

Revision history for this message
Michael Zanetti (mzanetti) wrote :

> I wonder if invoking dnd should be disabled on folded icons? Otherwise:
> * when two items are folded and you long press the bottom one, the one on top
> of it will be used

Disabled dragging of items with an angle > 20 degrees

> * there's no unfolding animation visible when invoking d'n'd on a folded icon

Added an animation when starting dragging

>
> Dropping at the top still results in some jumping.

Should be gone now. The last issue, that the top/bottom dropped items are still slightly folded is the Qt bug we're trying to fix.

>
> Weren't "other" launcher items supposed to dim on long press? They only dim
> onDrag now, somewhat tricky to see that it's "now" if only the item under your
> finger changes.

Nope, according to Martin's design they should only be dimmed once a dnd operation starts. I agree that its a bit hard to see if the finger covers the current item. However, this will improve once the quicklists are back because that one appears immediately on longpress and goes away the same time the other icons dim.

>
> Sometimes I end up with an empty space the size of an item that... Sometimes
> moves around, sometimes not. Dragging *inside* the launcher seems to be the
> easiest to reproduce.

Yes. I fear this is a Qt bug. Need to discuss this with Albert. If you make the window a bit smaller, and then drag into the progressiveScrollingarea you'll see that newly created delegates are positioned correctly. Seems like _sometimes_ the displace animation fails to start.

>
> I'd be OK with merging this if you say these should be fixed at a later
> iteration, though.

afaics only the Qt bugs left.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Michał Sawicz (saviq) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== renamed file 'Launcher/LauncherDelegate.qml' => 'Launcher/FoldingLauncherDelegate.qml'
2--- Launcher/LauncherDelegate.qml 2013-07-02 19:19:46 +0000
3+++ Launcher/FoldingLauncherDelegate.qml 2013-08-19 11:48:45 +0000
4@@ -17,149 +17,112 @@
5 import QtQuick 2.0
6 import Ubuntu.Components 0.1
7
8-Item {
9+LauncherDelegate {
10 id: root
11
12- property string iconName
13-
14- property real angle: 0
15- property bool highlighted: false
16- property real offset: 0
17- property alias brightness: transformEffect.brightness
18- property real itemOpacity: 1
19-
20- property real maxAngle: 0
21- property bool inverted: false
22-
23- readonly property int effectiveHeight: Math.cos(angle * Math.PI / 180) * height
24- readonly property real foldedHeight: Math.cos(maxAngle * Math.PI / 180) * height
25-
26- property bool dragging:false
27-
28- signal clicked()
29- signal longtap()
30- signal released()
31-
32- Item {
33- id: iconItem
34- width: parent.width
35- height: parent.height
36- anchors.centerIn: parent
37-
38- UbuntuShape {
39- id: iconShape
40- anchors.fill: parent
41- anchors.margins: units.gu(0.5)
42- radius: "medium"
43-
44- image: Image {
45- id: iconImage
46- sourceSize.width: iconShape.width
47- sourceSize.height: iconShape.height
48- source: "../graphics/applicationIcons/" + root.iconName + ".png"
49- }
50-
51- MouseArea {
52- id: mouseArea
53- anchors.fill: parent
54- onClicked: root.clicked()
55- onCanceled: root.released()
56- preventStealing: false
57-
58- onPressAndHold: {
59- root.state = "moving"
60- }
61- onReleased: {
62- root.state = "docked"
63- }
64- }
65- }
66- BorderImage {
67- id: overlayHighlight
68- anchors.centerIn: iconItem
69- rotation: inverted ? 180 : 0
70- source: isSelected ? "graphics/selected.sci" : "graphics/non-selected.sci"
71- width: root.width + units.gu(0.5)
72- height: width
73- property bool isSelected: root.highlighted || mouseArea.pressed
74- onIsSelectedChanged: shaderEffectSource.scheduleUpdate();
75- }
76- }
77-
78- ShaderEffect {
79- id: transformEffect
80- anchors.centerIn: parent
81- anchors.verticalCenterOffset: root.offset
82- width: parent.width
83- height: parent.height
84- property real itemOpacity: root.itemOpacity
85- property real brightness: 0
86- property real angle: root.angle
87- rotation: root.inverted ? 180 : 0
88-
89-
90- property variant source: ShaderEffectSource {
91- id: shaderEffectSource
92- sourceItem: iconItem
93- hideSource: true
94- live: false
95- }
96-
97- transform: [
98- // Rotating 3 times at top/bottom because that increases the perspective.
99- // This is a hack, but as QML does not support real 3D coordinates
100- // getting a higher perspective can only be done by a hack. This is the most
101- // readable/understandable one I could come up with.
102- Rotation {
103- axis { x: 1; y: 0; z: 0 }
104- origin { x: iconItem.width / 2; y: angle > 0 ? 0 : iconItem.height; z: 0 }
105- angle: root.angle * 0.7
106- },
107- Rotation {
108- axis { x: 1; y: 0; z: 0 }
109- origin { x: iconItem.width / 2; y: angle > 0 ? 0 : iconItem.height; z: 0 }
110- angle: root.angle * 0.7
111- },
112- Rotation {
113- axis { x: 1; y: 0; z: 0 }
114- origin { x: iconItem.width / 2; y: angle > 0 ? 0 : iconItem.height; z: 0 }
115- angle: root.angle * 0.7
116- },
117- // Because rotating it 3 times moves it more to the front/back, i.e. it gets
118- // bigger/smaller and we need a scale to compensate that again.
119- Scale {
120- xScale: 1 - (Math.abs(angle) / 500)
121- yScale: 1 - (Math.abs(angle) / 500)
122- origin { x: iconItem.width / 2; y: iconItem.height / 2}
123- }
124- ]
125-
126- // Using a fragment shader instead of QML's opacity and BrightnessContrast
127- // to be able to do both in one step which gives quite some better performance
128- fragmentShader: "
129- varying highp vec2 qt_TexCoord0;
130- uniform sampler2D source;
131- uniform lowp float brightness;
132- uniform lowp float itemOpacity;
133- void main(void)
134- {
135- highp vec4 sourceColor = texture2D(source, qt_TexCoord0);
136- sourceColor.rgb = mix(sourceColor.rgb, vec3(step(0.0, brightness)), abs(brightness));
137- sourceColor *= itemOpacity;
138- gl_FragColor = sourceColor;
139- }"
140+ // The angle used for rotating
141+ angle: {
142+ // First/last items are special
143+ if (index == 0 || index == priv.listView.count-1) {
144+ if (priv.distanceFromEdge < 0) {
145+ // proportion equation: distanceFromTopEdge : angle = totalUnfoldedHeight/2 : maxAngle
146+ return Math.max(-maxAngle, priv.distanceFromEdge * maxAngle / (priv.listView.foldingAreaHeight)) * priv.orientationFlag
147+ }
148+ return 0; // Don't fold first/last item as long as inside the view
149+ }
150+
151+ // Are we in the already completely outside the flickable? Fold for the last 5 degrees
152+ if (priv.distanceFromEdge < 0) {
153+ // proportion equation: -distanceFromTopEdge : angle = totalUnfoldedHeight : 5
154+ return Math.max(-maxAngle, (priv.distanceFromEdge * 5 / priv.listView.foldingAreaHeight) - (maxAngle-5)) * priv.orientationFlag
155+ }
156+
157+ // We are overlapping with the folding area, fold the icon to maxAngle - 5 degrees
158+ if (priv.overlapWithFoldingArea > 0) {
159+ // proportion equation: overlap: totalHeight = angle : (maxAngle - 5)
160+ return -priv.overlapWithFoldingArea * (maxAngle -5) / priv.listView.foldingAreaHeight * priv.orientationFlag;
161+ }
162+ return 0;
163+ }
164+
165+ // This is the offset that keeps the items inside the panel
166+ offset: {
167+ // First/last items are special
168+ if (index == 0 || index == priv.listView.count-1) {
169+ // Just keep them bound to the edges in case they're outside of the visible area
170+ if (priv.distanceFromEdge < 0) {
171+ return (-priv.distanceFromEdge - (height - effectiveHeight)) * priv.orientationFlag;
172+ }
173+ return 0;
174+ }
175+
176+ // Are we already completely outside the flickable? Stop the icon here.
177+ if (priv.distanceFromEdge < -priv.totalUnfoldedHeight) {
178+ return (-priv.distanceFromEdge - (root.height - effectiveHeight)) * priv.orientationFlag;
179+ }
180+
181+ // We're touching the edge, move slower than the actual flicking speed.
182+ if (priv.distanceFromEdge < 0) {
183+ return (Math.abs(priv.distanceFromEdge) * priv.totalEffectiveHeight / priv.totalUnfoldedHeight) * priv.orientationFlag
184+ }
185+ return 0;
186+ }
187+
188+ itemOpacity: {
189+ // First/last items are special
190+ if (index == 0 || index == priv.listView.count-1) {
191+ if (priv.distanceFromEdge < 0) {
192+ // Fade from 1 to 0 in the distance of 3 * foldingAreaHeight (which is when the next item reaches the edge)
193+ return 1.0 - (-priv.distanceFromEdge / (priv.listView.foldingAreaHeight * 3))
194+ }
195+ return 1; // Don't make first/last item transparent as long as inside the view
196+ }
197+
198+ // Are we already completely outside the flickable? Fade from 0.75 to 0 in 2 items height
199+ if (priv.distanceFromEdge < 0) {
200+ // proportion equation: -distanceFromEdge : 1-opacity = totalUnfoldedHeight : 0.75
201+ return 0.75 - (-priv.distanceFromEdge * 0.75 / (priv.totalUnfoldedHeight*2))
202+ }
203+
204+ // We are overlapping with the folding area, fade out to 0.75
205+ if (priv.overlapWithFoldingArea > 0) {
206+ // proportion equation: overlap : totalHeight = 1-opacity : 0.25
207+ return 1 - (priv.overlapWithFoldingArea * 0.25 / priv.listView.foldingAreaHeight)
208+ }
209+ return 1;
210+ }
211+
212+ brightness: {
213+ // First/last items are special
214+ if (index == 0 || index == priv.listView.count-1) {
215+ if (priv.distanceFromEdge < 0) {
216+ return -(-priv.distanceFromEdge / (priv.listView.foldingAreaHeight * 3))
217+ }
218+ return 0;
219+ }
220+ // Are we already completely outside the flickable? Fade from 0.7 to 0 in 2 items height
221+ if (priv.distanceFromEdge < 0) {
222+ return -0.3 - (-priv.distanceFromEdge * 0.1 / (priv.totalUnfoldedHeight*2))
223+ }
224+
225+ // We are overlapping with the folding area, fade out to 0.7
226+ if (priv.overlapWithFoldingArea > 0) {
227+ return - (priv.overlapWithFoldingArea * 0.3 / priv.listView.foldingAreaHeight)
228+ }
229+ return 0;
230 }
231
232 QtObject {
233 id: priv
234
235 property ListView listView: root.ListView.view
236- property real totalUnfoldedHeight: listView.itemSize + listView.spacing
237+ property real totalUnfoldedHeight: listView.itemHeight + listView.spacing
238 property real totalEffectiveHeight: effectiveHeight + listView.spacing
239 property real effectiveContentY: listView.contentY - listView.originY
240 property real effectiveY: y - listView.originY
241+ property real bottomDraggingDistanceOffset: listView.draggedIndex > index ? totalUnfoldedHeight : 0
242 property real distanceFromTopEdge: -(effectiveContentY + listView.topMargin - index*totalUnfoldedHeight)
243- property real distanceFromBottomEdge: listView.height - listView.bottomMargin - (effectiveY+height) + effectiveContentY
244+ property real distanceFromBottomEdge: listView.height - listView.bottomMargin - (effectiveY+height) + effectiveContentY + bottomDraggingDistanceOffset
245
246 property real distanceFromEdge: Math.abs(distanceFromBottomEdge) < Math.abs(distanceFromTopEdge) ? distanceFromBottomEdge : distanceFromTopEdge
247 property real orientationFlag: Math.abs(distanceFromBottomEdge) < Math.abs(distanceFromTopEdge) ? -1 : 1
248@@ -167,123 +130,4 @@
249 property real overlapWithFoldingArea: listView.foldingAreaHeight - distanceFromEdge
250 }
251
252- states: [
253- State {
254- name: "docked"
255- PropertyChanges {
256- target: root
257-
258- // This is the offset that keeps the items inside the panel
259- offset: {
260- // First/last items are special
261- if (index == 0 || index == priv.listView.count-1) {
262- // Just keep them bound to the edges in case they're outside of the visible area
263- if (priv.distanceFromEdge < 0) {
264- return (-priv.distanceFromEdge - (height - effectiveHeight)) * priv.orientationFlag;
265- }
266- return 0;
267- }
268-
269- // Are we already completely outside the flickable? Stop the icon here.
270- if (priv.distanceFromEdge < -priv.totalUnfoldedHeight) {
271- return (-priv.distanceFromEdge - (root.height - effectiveHeight)) * priv.orientationFlag;
272- }
273-
274- // We're touching the edge, move slower than the actual flicking speed.
275- if (priv.distanceFromEdge < 0) {
276- return (Math.abs(priv.distanceFromEdge) * priv.totalEffectiveHeight / priv.totalUnfoldedHeight) * priv.orientationFlag
277- }
278- return 0;
279- }
280-
281- // The angle used for rotating
282- angle: {
283- // First/last items are special
284- if (index == 0 || index == priv.listView.count-1) {
285- if (priv.distanceFromEdge < 0) {
286- // proportion equation: distanceFromTopEdge : angle = totalUnfoldedHeight/2 : maxAngle
287- return Math.max(-maxAngle, priv.distanceFromEdge * maxAngle / (priv.listView.foldingAreaHeight)) * priv.orientationFlag
288- }
289- return 0; // Don't fold first/last item as long as inside the view
290- }
291-
292- // Are we in the already completely outside the flickable? Fold for the last 5 degrees
293- if (priv.distanceFromEdge < 0) {
294- // proportion equation: -distanceFromTopEdge : angle = totalUnfoldedHeight : 5
295- return Math.max(-maxAngle, (priv.distanceFromEdge * 5 / priv.listView.foldingAreaHeight) - (maxAngle-5)) * priv.orientationFlag
296- }
297-
298- // We are overlapping with the folding area, fold the icon to maxAngle - 5 degrees
299- if (priv.overlapWithFoldingArea > 0) {
300- // proportion equation: overlap: totalHeight = angle : (maxAngle - 5)
301- return -priv.overlapWithFoldingArea * (maxAngle -5) / priv.listView.foldingAreaHeight * priv.orientationFlag;
302- }
303- return 0;
304- }
305-
306- itemOpacity: {
307- // First/last items are special
308- if (index == 0 || index == priv.listView.count-1) {
309- if (priv.distanceFromEdge < 0) {
310- // Fade from 1 to 0 in the distance of 3 * foldingAreaHeight (which is when the next item reaches the edge)
311- return 1.0 - (-priv.distanceFromEdge / (priv.listView.foldingAreaHeight * 3))
312- }
313- return 1; // Don't make first/last item transparent as long as inside the view
314- }
315-
316- // Are we already completely outside the flickable? Fade from 0.75 to 0 in 2 items height
317- if (priv.distanceFromEdge < 0) {
318- // proportion equation: -distanceFromEdge : 1-opacity = totalUnfoldedHeight : 0.75
319- return 0.75 - (-priv.distanceFromEdge * 0.75 / (priv.totalUnfoldedHeight*2))
320- }
321-
322- // We are overlapping with the folding area, fade out to 0.75
323- if (priv.overlapWithFoldingArea > 0) {
324- // proportion equation: overlap : totalHeight = 1-opacity : 0.25
325- return 1 - (priv.overlapWithFoldingArea * 0.25 / priv.listView.foldingAreaHeight)
326- }
327- return 1;
328- }
329-
330- brightness: {
331- // First/last items are special
332- if (index == 0 || index == priv.listView.count-1) {
333- if (priv.distanceFromEdge < 0) {
334- return -(-priv.distanceFromEdge / (priv.listView.foldingAreaHeight * 3))
335- }
336- return 0;
337- }
338- // Are we already completely outside the flickable? Fade from 0.7 to 0 in 2 items height
339- if (priv.distanceFromEdge < 0) {
340- return -0.3 - (-priv.distanceFromEdge * 0.1 / (priv.totalUnfoldedHeight*2))
341- }
342-
343- // We are overlapping with the folding area, fade out to 0.7
344- if (priv.overlapWithFoldingArea > 0) {
345- return - (priv.overlapWithFoldingArea * 0.3 / priv.listView.foldingAreaHeight)
346- }
347- return 0;
348- }
349- }
350- },
351-
352- State {
353- name: "moving"
354- PropertyChanges {
355- target: launcherDelegate;
356- offset: 0
357- angle: 0
358- }
359- PropertyChanges {
360- target: root
361- highlighted: true
362- dragging: true
363- }
364- PropertyChanges {
365- target: mouseArea
366- preventStealing: true
367- drag.target: root
368- }
369- }
370- ]
371 }
372
373=== modified file 'Launcher/Launcher.qml'
374--- Launcher/Launcher.qml 2013-07-05 11:34:14 +0000
375+++ Launcher/Launcher.qml 2013-08-19 11:48:45 +0000
376@@ -135,9 +135,9 @@
377 id: backgroundShade
378 anchors.fill: parent
379 color: "black"
380- opacity: root.state == "visible" ? 0.4 : 0
381+ opacity: root.state == "visible" ? 0.6 : 0
382
383- Behavior on opacity { NumberAnimation { duration: 200; easing.type: Easing.OutQuad} }
384+ Behavior on opacity { NumberAnimation { duration: UbuntuAnimation.BriskDuration } }
385 }
386
387 LauncherPanel {
388
389=== added file 'Launcher/LauncherDelegate.qml'
390--- Launcher/LauncherDelegate.qml 1970-01-01 00:00:00 +0000
391+++ Launcher/LauncherDelegate.qml 2013-08-19 11:48:45 +0000
392@@ -0,0 +1,137 @@
393+/*
394+ * Copyright (C) 2013 Canonical, Ltd.
395+ *
396+ * This program is free software; you can redistribute it and/or modify
397+ * it under the terms of the GNU General Public License as published by
398+ * the Free Software Foundation; version 3.
399+ *
400+ * This program is distributed in the hope that it will be useful,
401+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
402+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
403+ * GNU General Public License for more details.
404+ *
405+ * You should have received a copy of the GNU General Public License
406+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
407+ */
408+
409+import QtQuick 2.0
410+import Ubuntu.Components 0.1
411+
412+Item {
413+ id: root
414+
415+ property string iconName
416+
417+ property bool highlighted: false
418+ property real maxAngle: 0
419+ property bool inverted: false
420+
421+ readonly property int effectiveHeight: Math.cos(angle * Math.PI / 180) * itemHeight
422+ readonly property real foldedHeight: Math.cos(maxAngle * Math.PI / 180) * itemHeight
423+
424+ property int itemWidth
425+ property int itemHeight
426+ // The angle used for rotating
427+ property real angle: 0
428+ // This is the offset that keeps the items inside the panel
429+ property real offset: 0
430+ property real itemOpacity: 1
431+ property real brightness: 0
432+
433+ Item {
434+ id: iconItem
435+ width: parent.itemWidth
436+ height: parent.itemHeight
437+ anchors.centerIn: parent
438+
439+ UbuntuShape {
440+ id: iconShape
441+ anchors.fill: parent
442+ anchors.margins: units.gu(0.5)
443+ radius: "medium"
444+
445+ image: Image {
446+ id: iconImage
447+ sourceSize.width: iconShape.width
448+ sourceSize.height: iconShape.height
449+ source: "../graphics/applicationIcons/" + iconName + ".png"
450+ property string iconName: root.iconName
451+ onIconNameChanged: shaderEffectSource.scheduleUpdate();
452+ }
453+ }
454+
455+ BorderImage {
456+ id: overlayHighlight
457+ anchors.centerIn: iconItem
458+ rotation: inverted ? 180 : 0
459+ source: isSelected ? "graphics/selected.sci" : "graphics/non-selected.sci"
460+ width: root.itemWidth + units.gu(0.5)
461+ height: root.itemHeight + units.gu(0.5)
462+ property bool isSelected: root.highlighted
463+ onIsSelectedChanged: shaderEffectSource.scheduleUpdate();
464+ }
465+ }
466+
467+ ShaderEffect {
468+ id: transformEffect
469+ anchors.centerIn: parent
470+ anchors.verticalCenterOffset: root.offset
471+ width: parent.itemWidth
472+ height: parent.itemHeight
473+ property real itemOpacity: root.itemOpacity
474+ property real brightness: Math.max(-1, root.brightness)
475+ property real angle: root.angle
476+ rotation: root.inverted ? 180 : 0
477+
478+ property variant source: ShaderEffectSource {
479+ id: shaderEffectSource
480+ sourceItem: iconItem
481+ hideSource: true
482+ live: false
483+ }
484+
485+ transform: [
486+ // Rotating 3 times at top/bottom because that increases the perspective.
487+ // This is a hack, but as QML does not support real 3D coordinates
488+ // getting a higher perspective can only be done by a hack. This is the most
489+ // readable/understandable one I could come up with.
490+ Rotation {
491+ axis { x: 1; y: 0; z: 0 }
492+ origin { x: iconItem.width / 2; y: angle > 0 ? 0 : iconItem.height; z: 0 }
493+ angle: root.angle * 0.7
494+ },
495+ Rotation {
496+ axis { x: 1; y: 0; z: 0 }
497+ origin { x: iconItem.width / 2; y: angle > 0 ? 0 : iconItem.height; z: 0 }
498+ angle: root.angle * 0.7
499+ },
500+ Rotation {
501+ axis { x: 1; y: 0; z: 0 }
502+ origin { x: iconItem.width / 2; y: angle > 0 ? 0 : iconItem.height; z: 0 }
503+ angle: root.angle * 0.7
504+ },
505+ // Because rotating it 3 times moves it more to the front/back, i.e. it gets
506+ // bigger/smaller and we need a scale to compensate that again.
507+ Scale {
508+ xScale: 1 - (Math.abs(angle) / 500)
509+ yScale: 1 - (Math.abs(angle) / 500)
510+ origin { x: iconItem.width / 2; y: iconItem.height / 2}
511+ }
512+ ]
513+
514+ // Using a fragment shader instead of QML's opacity and BrightnessContrast
515+ // to be able to do both in one step which gives quite some better performance
516+ fragmentShader: "
517+ varying highp vec2 qt_TexCoord0;
518+ uniform sampler2D source;
519+ uniform lowp float brightness;
520+ uniform lowp float itemOpacity;
521+ void main(void)
522+ {
523+ highp vec4 sourceColor = texture2D(source, qt_TexCoord0);
524+ sourceColor.rgb = mix(sourceColor.rgb, vec3(step(0.0, brightness)), abs(brightness));
525+ sourceColor *= itemOpacity;
526+ gl_FragColor = sourceColor;
527+ }"
528+ }
529+}
530
531=== modified file 'Launcher/LauncherPanel.qml'
532--- Launcher/LauncherPanel.qml 2013-07-11 08:19:10 +0000
533+++ Launcher/LauncherPanel.qml 2013-08-19 11:48:45 +0000
534@@ -16,6 +16,7 @@
535
536 import QtQuick 2.0
537 import Ubuntu.Components 0.1
538+import Ubuntu.Components.ListItems 0.1 as ListItems
539 import Unity 0.1
540 import Unity.Launcher 0.1
541 import "../Components/ListItems"
542@@ -28,54 +29,33 @@
543 property var model
544 property bool inverted: true
545 property bool dragging: false
546- property bool moving: launcherListView.moving || launcherListView.flicking
547- property int dragPosition: 0
548+ property bool moving: launcherListView.moving || launcherListView.flicking || dndArea.draggedIndex >= 0
549 property int highlightIndex: -1
550
551 signal applicationSelected(string desktopFile)
552 signal dashItemSelected(int index)
553
554- onDragPositionChanged: {
555- var effectiveDragPosition = root.inverted ? launcherListView.height - dragPosition : dragPosition - mainColumn.anchors.margins
556-
557- var hiddenContentHeight = launcherListView.contentHeight - launcherListView.height
558- // Shortening scrollable height because the first/last item needs to be fully expanded before reaching the top/bottom
559- var scrollableHeight = launcherListView.height - (launcherListView.itemSize + launcherColumn.spacing) *2
560- // As we shortened the scrollableHeight, lets move everything down by the itemSize
561- var shortenedEffectiveDragPosition = effectiveDragPosition - launcherListView.itemSize - launcherColumn.spacing
562- var newContentY = shortenedEffectiveDragPosition * hiddenContentHeight / scrollableHeight
563-
564- // limit top/bottom to prevent overshooting
565- launcherListView.contentY = Math.min(hiddenContentHeight, Math.max(0, newContentY));
566-
567- // Now calculate the current index:
568- // > the current mouse position + the hidden/scolled content on top is the mouse position in the averall view
569- // > adjust that removing all the margins
570- // > divide by itemSize to get index
571- highlightIndex = (effectiveDragPosition + launcherListView.contentY - mainColumn.anchors.margins*3 - launcherColumn.spacing/2) / (launcherListView.itemSize + launcherColumn.spacing)
572- }
573-
574 BorderImage {
575 id: background
576 source: "graphics/launcher_bg.sci"
577 anchors.fill: parent
578+ anchors.rightMargin: root.inverted ? 0 : -units.gu(1)
579+ anchors.leftMargin: root.inverted ? -units.gu(1) : 0
580+ rotation: root.rotation
581 }
582
583 Column {
584 id: mainColumn
585 anchors {
586 fill: parent
587- topMargin: units.gu(0.5)
588- bottomMargin: units.gu(1)
589 leftMargin: units.gu(0.5)
590 rightMargin: units.gu(0.5)
591 }
592- spacing: units.gu(0.5)
593
594 MouseArea {
595 id: dashItem
596 width: parent.width
597- height: units.gu(6.5)
598+ height: units.gu(7)
599 onClicked: root.dashItemSelected(0)
600 z: 1
601 Image {
602@@ -100,87 +80,336 @@
603 anchors.left: parent.left
604 anchors.right: parent.right
605 height: parent.height - dashItem.height - parent.spacing*2
606- ListView {
607- id: launcherListView
608- objectName: "launcherListView"
609+
610+ Item {
611 anchors.fill: parent
612- anchors.topMargin: -itemSize
613- anchors.bottomMargin: -itemSize
614- topMargin: itemSize
615- bottomMargin: itemSize
616- height: parent.height - dashItem.height - parent.spacing*2
617- model: root.model
618- cacheBuffer: itemSize * 3
619- snapMode: ListView.SnapToItem
620- highlightRangeMode: ListView.ApplyRange
621- preferredHighlightBegin: (height - itemSize) / 2
622- preferredHighlightEnd: (height + itemSize) / 2
623-
624- // The height of the area where icons start getting folded
625- property int foldingAreaHeight: itemSize * 0.75
626- property int itemSize: width
627- property int clickFlickSpeed: units.gu(60)
628-
629- delegate: LauncherDelegate {
630- id: launcherDelegate
631- objectName: "launcherDelegate" + index
632- width: launcherListView.itemSize
633- height: launcherListView.itemSize
634- iconName: model.icon
635- inverted: root.inverted
636- highlighted: root.dragging && index === root.highlightIndex
637- z: -Math.abs(offset)
638- state: "docked"
639- maxAngle: 60
640-
641- onClicked: {
642- // First/last item do the scrolling at more than 12 degrees
643- if (index == 0 || index == launcherListView.count -1) {
644- if (angle > 12) {
645+ clip: true
646+
647+ ListView {
648+ id: launcherListView
649+ objectName: "launcherListView"
650+ anchors {
651+ fill: parent
652+ topMargin: -extensionSize + units.gu(0.5)
653+ bottomMargin: -extensionSize + units.gu(1)
654+ }
655+ topMargin: extensionSize
656+ bottomMargin: extensionSize
657+ height: parent.height - dashItem.height - parent.spacing*2
658+ model: root.model
659+ cacheBuffer: itemHeight * 3
660+ snapMode: interactive ? ListView.SnapToItem : ListView.NoSnap
661+ highlightRangeMode: ListView.ApplyRange
662+ preferredHighlightBegin: (height - itemHeight) / 2
663+ preferredHighlightEnd: (height + itemHeight) / 2
664+ spacing: units.gu(0.5)
665+
666+ // The size of the area the ListView is extended to make sure items are not
667+ // destroyed when dragging them outside the list. This needs to be at least
668+ // itemHeight to prevent folded items from disappearing and DragArea limits
669+ // need to be smaller than this size to avoid breakage.
670+ property int extensionSize: itemHeight * 3
671+
672+ // The height of the area where icons start getting folded
673+ property int foldingAreaHeight: itemHeight * 0.75
674+ property int itemWidth: width
675+ property int itemHeight: width * 7.5 / 8
676+ property int clickFlickSpeed: units.gu(60)
677+ property int draggedIndex: dndArea.draggedIndex
678+ property real realContentY: contentY - originY + topMargin
679+ property int realItemHeight: itemHeight + spacing
680+
681+ displaced: Transition {
682+ NumberAnimation { properties: "x,y"; duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing }
683+ }
684+
685+ delegate: FoldingLauncherDelegate {
686+ id: launcherDelegate
687+ objectName: "launcherDelegate" + index
688+ itemHeight: launcherListView.itemHeight
689+ itemWidth: launcherListView.itemWidth
690+ width: itemWidth
691+ height: itemHeight
692+ iconName: model.icon
693+ inverted: root.inverted
694+ highlighted: dragging && index === root.highlightIndex
695+ z: -Math.abs(offset)
696+ maxAngle: 60
697+ property bool dragging: false
698+
699+ ThinDivider {
700+ id: dropIndicator
701+ objectName: "dropIndicator"
702+ anchors.centerIn: parent
703+ width: parent.width + mainColumn.anchors.leftMargin + mainColumn.anchors.rightMargin
704+ opacity: 0
705+ }
706+
707+ states: [
708+ State {
709+ name: "selected"
710+ when: dndArea.selectedItem === launcherDelegate && fakeDragItem.visible && !dragging
711+ PropertyChanges {
712+ target: launcherDelegate
713+ itemOpacity: 0
714+ }
715+ },
716+ State {
717+ name: "dragging"
718+ when: dragging
719+ PropertyChanges {
720+ target: launcherDelegate
721+ height: units.gu(1)
722+ itemOpacity: 0
723+ }
724+ PropertyChanges {
725+ target: dropIndicator
726+ opacity: 1
727+ }
728+ },
729+ State {
730+ name: "expanded"
731+ when: dndArea.draggedIndex >= 0 && (dndArea.preDragging || dndArea.dragging || dndArea.postDragging) && dndArea.draggedIndex != index
732+ PropertyChanges {
733+ target: launcherDelegate
734+ angle: 0
735+ offset: 0
736+ itemOpacity: 0.6
737+ }
738+ }
739+ ]
740+
741+ transitions: [
742+ Transition {
743+ from: ""
744+ to: "selected"
745+ NumberAnimation { properties: "itemOpacity"; duration: UbuntuAnimation.FastDuration }
746+ },
747+ Transition {
748+ from: "*"
749+ to: "expanded"
750+ NumberAnimation { properties: "itemOpacity"; duration: UbuntuAnimation.FastDuration }
751+ UbuntuNumberAnimation { properties: "angle,offset" }
752+ },
753+ Transition {
754+ from: "expanded"
755+ to: ""
756+ NumberAnimation { properties: "itemOpacity"; duration: UbuntuAnimation.BriskDuration }
757+ UbuntuNumberAnimation { properties: "angle,offset" }
758+ },
759+ Transition {
760+ from: "selected"
761+ to: "dragging"
762+ UbuntuNumberAnimation { properties: "height" }
763+ NumberAnimation { target: dropIndicator; properties: "opacity"; duration: UbuntuAnimation.FastDuration }
764+ },
765+ Transition {
766+ from: "dragging"
767+ to: "*"
768+ NumberAnimation { target: dropIndicator; properties: "opacity"; duration: UbuntuAnimation.FastDuration }
769+ NumberAnimation { properties: "itemOpacity"; duration: UbuntuAnimation.BriskDuration }
770+ SequentialAnimation {
771+ UbuntuNumberAnimation { properties: "height" }
772+ PropertyAction { target: dndArea; property: "postDragging"; value: false }
773+ PropertyAction { target: dndArea; property: "draggedIndex"; value: -1 }
774+ }
775+ }
776+ ]
777+ }
778+
779+ MouseArea {
780+ id: dndArea
781+ anchors {
782+ fill: parent
783+ topMargin: launcherListView.topMargin
784+ bottomMargin: launcherListView.bottomMargin
785+ }
786+ drag.minimumY: -launcherListView.topMargin
787+ drag.maximumY: height + launcherListView.bottomMargin
788+
789+ property int draggedIndex: -1
790+ property var selectedItem
791+ property bool preDragging: false
792+ property bool dragging: selectedItem !== undefined && selectedItem.dragging
793+ property bool postDragging: false
794+ property int startX
795+ property int startY
796+
797+ onPressed: {
798+ selectedItem = launcherListView.itemAt(mouseX, mouseY + launcherListView.realContentY)
799+ selectedItem.highlighted = true
800+ }
801+
802+ onClicked: {
803+ var index = Math.floor((mouseY + launcherListView.realContentY) / launcherListView.realItemHeight);
804+ var clickedItem = launcherListView.itemAt(mouseX, mouseY + launcherListView.realContentY)
805+
806+ // First/last item do the scrolling at more than 12 degrees
807+ if (index == 0 || index == launcherListView.count - 1) {
808+ if (clickedItem.angle > 12) {
809+ launcherListView.flick(0, -launcherListView.clickFlickSpeed);
810+ } else if (clickedItem.angle < -12) {
811+ launcherListView.flick(0, launcherListView.clickFlickSpeed);
812+ } else {
813+ root.applicationSelected(LauncherModel.get(index).desktopFile);
814+ }
815+ return;
816+ }
817+
818+ // the rest launches apps up to an angle of 30 degrees
819+ if (clickedItem.angle > 30) {
820 launcherListView.flick(0, -launcherListView.clickFlickSpeed);
821- } else if (angle < -12) {
822+ } else if (clickedItem.angle < -30) {
823 launcherListView.flick(0, launcherListView.clickFlickSpeed);
824 } else {
825 root.applicationSelected(LauncherModel.get(index).desktopFile);
826 }
827- return;
828- }
829-
830- // the rest launches apps up to an angle of 30 degrees
831- if (angle > 30) {
832- launcherListView.flick(0, -launcherListView.clickFlickSpeed);
833- } else if (angle < -30) {
834- launcherListView.flick(0, launcherListView.clickFlickSpeed);
835- } else {
836- root.applicationSelected(LauncherModel.get(index).desktopFile);
837- }
838- }
839- }
840-
841- MouseArea {
842- id: topFoldingArea
843- anchors {
844- left: parent.left
845- right: parent.right
846- top: parent.top
847- topMargin: launcherListView.topMargin
848- }
849- height: launcherListView.itemSize / 2
850- enabled: launcherListView.contentY > -launcherListView.topMargin
851- onClicked: launcherListView.flick(0, launcherListView.clickFlickSpeed)
852- }
853-
854- MouseArea {
855- id: bottomFoldingArea
856- anchors {
857- left: parent.left
858- right: parent.right
859- bottom: parent.bottom
860- bottomMargin: launcherListView.bottomMargin
861- }
862- height: launcherListView.itemSize / 2
863- enabled: launcherListView.contentHeight - launcherListView.height - launcherListView.contentY > -launcherListView.bottomMargin
864- onClicked: launcherListView.flick(0, -launcherListView.clickFlickSpeed)
865+ }
866+
867+ onCanceled: {
868+ selectedItem.highlighted = false
869+ selectedItem = undefined;
870+ preDragging = false;
871+ postDragging = false;
872+ }
873+
874+ onReleased: {
875+ var droppedIndex = draggedIndex;
876+ if (dragging) {
877+ postDragging = true;
878+ } else {
879+ draggedIndex = -1;
880+ }
881+
882+ selectedItem.highlighted = false
883+ selectedItem.dragging = false;
884+ selectedItem = undefined;
885+ preDragging = false;
886+
887+ drag.target = undefined
888+
889+ progressiveScrollingTimer.stop();
890+ launcherListView.interactive = true;
891+ if (droppedIndex >= launcherListView.count - 2 && postDragging) {
892+ launcherListView.flick(0, -launcherListView.clickFlickSpeed);
893+ }
894+ }
895+
896+ onPressAndHold: {
897+ if (Math.abs(selectedItem.angle) > 30) {
898+ return;
899+ }
900+
901+ draggedIndex = Math.floor((mouseY + launcherListView.realContentY) / launcherListView.realItemHeight);
902+
903+ launcherListView.interactive = false
904+
905+ var yOffset = draggedIndex > 0 ? (mouseY + launcherListView.realContentY) % (draggedIndex * launcherListView.realItemHeight) : mouseY + launcherListView.realContentY
906+
907+ fakeDragItem.iconName = launcherListView.model.get(draggedIndex).icon
908+ fakeDragItem.x = 0
909+ fakeDragItem.y = mouseY - yOffset + launcherListView.anchors.topMargin + launcherListView.topMargin
910+ fakeDragItem.angle = selectedItem.angle * (root.inverted ? -1 : 1)
911+ fakeDragItem.offset = selectedItem.offset * (root.inverted ? -1 : 1)
912+ fakeDragItem.flatten()
913+ drag.target = fakeDragItem
914+
915+ startX = mouseX
916+ startY = mouseY
917+ }
918+
919+ onPositionChanged: {
920+ if (draggedIndex >= 0) {
921+ if (!selectedItem.dragging) {
922+ var distance = Math.max(Math.abs(mouseX - startX), Math.abs(mouseY - startY))
923+ if (!preDragging && distance > units.gu(1.5)) {
924+ preDragging = true;
925+ }
926+ if (distance > launcherListView.itemHeight) {
927+ selectedItem.dragging = true
928+ preDragging = false;
929+ }
930+ }
931+ if (!selectedItem.dragging) {
932+ return
933+ }
934+
935+ var itemCenterY = fakeDragItem.y + fakeDragItem.height / 2
936+
937+ // Move it down by the the missing size to compensate index calculation with only expanded items
938+ itemCenterY += (launcherListView.itemHeight - selectedItem.height) / 2
939+
940+ if (mouseY > launcherListView.height - launcherListView.topMargin - launcherListView.bottomMargin - launcherListView.realItemHeight) {
941+ progressiveScrollingTimer.downwards = false
942+ progressiveScrollingTimer.start()
943+ } else if (mouseY < launcherListView.realItemHeight) {
944+ progressiveScrollingTimer.downwards = true
945+ progressiveScrollingTimer.start()
946+ } else {
947+ progressiveScrollingTimer.stop()
948+ }
949+
950+ var newIndex = (itemCenterY + launcherListView.realContentY) / launcherListView.realItemHeight
951+
952+ if (newIndex > draggedIndex + 1) {
953+ newIndex = draggedIndex + 1
954+ } else if (newIndex < draggedIndex) {
955+ newIndex = draggedIndex -1
956+ } else {
957+ return
958+ }
959+
960+ if (newIndex >= 0 && newIndex < launcherListView.count) {
961+ launcherListView.model.move(draggedIndex, newIndex)
962+ draggedIndex = newIndex
963+ }
964+ }
965+ }
966+ }
967+ Timer {
968+ id: progressiveScrollingTimer
969+ interval: 2
970+ repeat: true
971+ running: false
972+ property bool downwards: true
973+ onTriggered: {
974+ if (downwards) {
975+ var minY = -launcherListView.topMargin
976+ if (launcherListView.contentY > minY) {
977+ launcherListView.contentY = Math.max(launcherListView.contentY - units.dp(2), minY)
978+ }
979+ } else {
980+ var maxY = launcherListView.contentHeight - launcherListView.height + launcherListView.topMargin + launcherListView.originY
981+ if (launcherListView.contentY < maxY) {
982+ launcherListView.contentY = Math.min(launcherListView.contentY + units.dp(2), maxY)
983+ }
984+ }
985+ }
986+ }
987+ }
988+ }
989+
990+ LauncherDelegate {
991+ id: fakeDragItem
992+ objectName: "fakeDragItem"
993+ visible: dndArea.draggedIndex >= 0 && !dndArea.postDragging
994+ itemWidth: launcherListView.itemWidth
995+ itemHeight: launcherListView.itemHeight
996+ height: itemHeight
997+ width: itemWidth
998+ rotation: root.rotation
999+ highlighted: true
1000+ itemOpacity: 0.8
1001+
1002+ function flatten() {
1003+ fakeDragItemAnimation.start();
1004+ }
1005+
1006+ UbuntuNumberAnimation {
1007+ id: fakeDragItemAnimation
1008+ target: fakeDragItem;
1009+ properties: "angle,offset";
1010+ to: 0
1011 }
1012 }
1013 }
1014
1015=== removed file 'Launcher/graphics/launcher_bg@18.png'
1016Binary files Launcher/graphics/launcher_bg@18.png 2013-06-05 22:03:08 +0000 and Launcher/graphics/launcher_bg@18.png 1970-01-01 00:00:00 +0000 differ
1017=== removed file 'Launcher/graphics/launcher_bg@18.sci'
1018--- Launcher/graphics/launcher_bg@18.sci 2013-06-05 22:03:08 +0000
1019+++ Launcher/graphics/launcher_bg@18.sci 1970-01-01 00:00:00 +0000
1020@@ -1,5 +0,0 @@
1021-border.left: 0
1022-border.top: 0
1023-border.bottom: 0
1024-border.right: 24
1025-source: launcher_bg@18.png
1026
1027=== added file 'Launcher/graphics/launcher_bg@30.png'
1028Binary files Launcher/graphics/launcher_bg@30.png 1970-01-01 00:00:00 +0000 and Launcher/graphics/launcher_bg@30.png 2013-08-19 11:48:45 +0000 differ
1029=== added file 'Launcher/graphics/launcher_bg@30.sci'
1030--- Launcher/graphics/launcher_bg@30.sci 1970-01-01 00:00:00 +0000
1031+++ Launcher/graphics/launcher_bg@30.sci 2013-08-19 11:48:45 +0000
1032@@ -0,0 +1,5 @@
1033+border.left: 4
1034+border.top: 4
1035+border.bottom: 4
1036+border.right: 66
1037+source: launcher_bg@30.png
1038
1039=== modified file 'plugins/Unity/Launcher/backend/launcherbackend.cpp'
1040--- plugins/Unity/Launcher/backend/launcherbackend.cpp 2013-07-16 20:45:13 +0000
1041+++ plugins/Unity/Launcher/backend/launcherbackend.cpp 2013-08-19 11:48:45 +0000
1042@@ -101,7 +101,7 @@
1043 {
1044 // TODO: return app's pinned state from settings
1045 Q_UNUSED(appId)
1046- return true;
1047+ return false;
1048 }
1049
1050 void LauncherBackend::setPinned(const QString &appId, bool pinned)
1051
1052=== modified file 'plugins/Unity/Launcher/launcheritem.cpp'
1053--- plugins/Unity/Launcher/launcheritem.cpp 2013-07-08 13:55:07 +0000
1054+++ plugins/Unity/Launcher/launcheritem.cpp 2013-08-19 11:48:45 +0000
1055@@ -33,7 +33,10 @@
1056 m_count(0),
1057 m_quickList(new QuickListModel(this))
1058 {
1059-
1060+ QuickListEntry pinningAction;
1061+ pinningAction.setActionId("pin_item");
1062+ pinningAction.setText("Pin to Launcher");
1063+ m_quickList->appendAction(pinningAction);
1064 }
1065
1066 QString LauncherItem::appId() const
1067@@ -65,6 +68,10 @@
1068 {
1069 if (m_pinned != pinned) {
1070 m_pinned = pinned;
1071+ QuickListEntry entry;
1072+ entry.setActionId("pin_item");
1073+ entry.setText(pinned ? "Remove from Launcher": "Pin to Launcher");
1074+ m_quickList->updateAction(entry);
1075 Q_EMIT pinnedChanged(pinned);
1076 }
1077 }
1078
1079=== modified file 'plugins/Unity/Launcher/launchermodel.cpp'
1080--- plugins/Unity/Launcher/launchermodel.cpp 2013-07-08 17:01:49 +0000
1081+++ plugins/Unity/Launcher/launchermodel.cpp 2013-08-19 11:48:45 +0000
1082@@ -82,8 +82,25 @@
1083
1084 void LauncherModel::move(int oldIndex, int newIndex)
1085 {
1086- // Perform the move in our list
1087- beginMoveRows(QModelIndex(), oldIndex, oldIndex, QModelIndex(), newIndex);
1088+ // Make sure its not moved outside the lists
1089+ if (newIndex < 0) {
1090+ newIndex = 0;
1091+ }
1092+ if (newIndex >= m_list.count()) {
1093+ newIndex = m_list.count()-1;
1094+ }
1095+
1096+ // Nothing to do?
1097+ if (oldIndex == newIndex) {
1098+ return;
1099+ }
1100+
1101+ // QList's and QAbstractItemModel's move implementation differ when moving an item up the list :/
1102+ // While QList needs the index in the resulting list, beginMoveRows expects it to be in the current list
1103+ // adjust the model's index by +1 in case we're moving upwards
1104+ int newModelIndex = newIndex > oldIndex ? newIndex+1 : newIndex;
1105+
1106+ beginMoveRows(QModelIndex(), oldIndex, oldIndex, QModelIndex(), newModelIndex);
1107 m_list.move(oldIndex, newIndex);
1108 endMoveRows();
1109
1110@@ -140,10 +157,23 @@
1111 return;
1112 }
1113
1114- QuickListModel *model = qobject_cast<QuickListModel*>(m_list.at(index)->quickList());
1115+ LauncherItem *item = m_list.at(index);
1116+ QuickListModel *model = qobject_cast<QuickListModel*>(item->quickList());
1117 if (model) {
1118 QString actionId = model->get(actionIndex).actionId();
1119- m_backend->triggerQuickListAction(appId, actionId);
1120+
1121+ // Check if this is one of the launcher actions we handle ourselves
1122+ if (actionId == "pin_item") {
1123+ if (item->pinned()) {
1124+ requestRemove(appId);
1125+ } else {
1126+ pin(appId);
1127+ }
1128+
1129+ // Nope, we don't know this action, let the backend forward it to the application
1130+ } else {
1131+ m_backend->triggerQuickListAction(appId, actionId);
1132+ }
1133 }
1134 }
1135
1136
1137=== modified file 'plugins/Unity/Launcher/plugin.cpp'
1138--- plugins/Unity/Launcher/plugin.cpp 2013-07-08 17:00:27 +0000
1139+++ plugins/Unity/Launcher/plugin.cpp 2013-08-19 11:48:45 +0000
1140@@ -41,7 +41,9 @@
1141
1142 qmlRegisterUncreatableType<LauncherModelInterface>(uri, 0, 1, "LauncherModelInterface", "Abstract Interface. Cannot be instantiated.");
1143 qmlRegisterUncreatableType<LauncherItemInterface>(uri, 0, 1, "LauncherItemInterface", "Abstract Interface. Cannot be instantiated.");
1144+ qmlRegisterUncreatableType<QuickListModelInterface>(uri, 0, 1, "QuickListInterface", "Abstract Interface. Cannot be instantiated.");
1145
1146 qmlRegisterSingletonType<LauncherModel>(uri, 0, 1, "LauncherModel", modelProvider);
1147 qmlRegisterUncreatableType<LauncherItem>(uri, 0, 1, "LauncherItem", "Can't create new Launcher Items in QML. Get them from the LauncherModel.");
1148+ qmlRegisterUncreatableType<QuickListModel>(uri, 0, 1, "QuickListModel", "Can't create a QuickListModel in QML. Get them from the LauncherItems.");
1149 }
1150
1151=== modified file 'plugins/Unity/Launcher/quicklistmodel.cpp'
1152--- plugins/Unity/Launcher/quicklistmodel.cpp 2013-07-08 13:55:07 +0000
1153+++ plugins/Unity/Launcher/quicklistmodel.cpp 2013-08-19 11:48:45 +0000
1154@@ -37,6 +37,17 @@
1155 endInsertRows();
1156 }
1157
1158+void QuickListModel::updateAction(const QuickListEntry &entry)
1159+{
1160+ for (int i = 0; i < m_list.count(); ++i) {
1161+ if (m_list.at(i).actionId() == entry.actionId()) {
1162+ m_list.replace(i, entry);
1163+ Q_EMIT dataChanged(index(i), index(i));
1164+ return;
1165+ }
1166+ }
1167+}
1168+
1169 QuickListEntry QuickListModel::get(int index) const
1170 {
1171 return m_list.at(index);
1172
1173=== modified file 'plugins/Unity/Launcher/quicklistmodel.h'
1174--- plugins/Unity/Launcher/quicklistmodel.h 2013-07-08 13:55:07 +0000
1175+++ plugins/Unity/Launcher/quicklistmodel.h 2013-08-19 11:48:45 +0000
1176@@ -36,6 +36,16 @@
1177 ~QuickListModel();
1178
1179 void appendAction(const QuickListEntry &entry);
1180+
1181+ /**
1182+ * @brief Update an existing action
1183+ * @param entry The new, updated entry
1184+ *
1185+ * This will only do something if entry.actionId is found in the model.
1186+ * To add a new entry, use appendAction().
1187+ */
1188+ void updateAction(const QuickListEntry &entry);
1189+
1190 QuickListEntry get(int index) const;
1191
1192 int rowCount(const QModelIndex &parent = QModelIndex()) const;
1193
1194=== modified file 'tests/qmltests/Launcher/tst_Launcher.qml'
1195--- tests/qmltests/Launcher/tst_Launcher.qml 2013-07-05 09:02:07 +0000
1196+++ tests/qmltests/Launcher/tst_Launcher.qml 2013-08-19 11:48:45 +0000
1197@@ -19,6 +19,7 @@
1198 import Unity.Test 0.1 as UT
1199 import ".."
1200 import "../../../Launcher"
1201+import Unity.Launcher 0.1
1202
1203 /* Nothing is shown at first. If you drag from left edge you will bring up the
1204 launcher. */
1205@@ -247,4 +248,63 @@
1206 }
1207 */
1208 }
1209+
1210+ UT.UnityTestCase {
1211+ id: dndTestCase
1212+ name: "Drag and Drop"
1213+ when: windowShown && initTestCase.completed
1214+
1215+ function test_dragndrop_data() {
1216+ return [
1217+ {tag: "startDrag", fullDrag: false},
1218+ {tag: "fullDrag", fullDrag: true},
1219+ ];
1220+ }
1221+
1222+ function test_dragndrop(data) {
1223+ revealer.dragLauncherIntoView();
1224+ var draggedItem = findChild(launcher, "launcherDelegate5")
1225+ var item0 = findChild(launcher, "launcherDelegate0")
1226+ var fakeDragItem = findChild(launcher, "fakeDragItem")
1227+ var initialItemHeight = draggedItem.height
1228+ var item5Name = LauncherModel.get(5).iconName
1229+ var item4Name = LauncherModel.get(4).iconName
1230+
1231+ // Initial state
1232+ compare(draggedItem.itemOpacity, 1, "Item's opacity is not 1 at beginning")
1233+ compare(fakeDragItem.visible, false, "FakeDragItem isn't invisible at the beginning")
1234+ tryCompare(findChild(draggedItem, "dropIndicator"), "opacity", 0)
1235+
1236+ // Doing longpress
1237+ mousePress(draggedItem, draggedItem.width / 2, draggedItem.height / 2)
1238+ // DraggedItem needs to hide and fakeDragItem become visible
1239+ tryCompare(draggedItem, "itemOpacity", 0)
1240+ tryCompare(fakeDragItem, "visible", true)
1241+
1242+ // Dragging a bit (> 1.5 gu)
1243+ mouseMove(draggedItem, -units.gu(2), draggedItem.height / 2)
1244+ // Other items need to expand and become 0.6 opaque
1245+ tryCompare(item0, "angle", 0)
1246+ tryCompare(item0, "itemOpacity", 0.6)
1247+
1248+ if (data.fullDrag) {
1249+ // Dragging a bit more
1250+ mouseMove(draggedItem, -units.gu(15), draggedItem.height / 2, 100)
1251+ tryCompare(findChild(draggedItem, "dropIndicator"), "opacity", 1)
1252+ tryCompare(draggedItem, "height", units.gu(1))
1253+
1254+ // Dragging downwards. Item needs to move in the model
1255+ mouseMove(draggedItem, -units.gu(15), -initialItemHeight)
1256+ waitForRendering(draggedItem)
1257+ compare(LauncherModel.get(5).iconName, item4Name)
1258+ compare(LauncherModel.get(5).iconName, item5Name)
1259+ }
1260+
1261+ // Releasing and checking if initial values are restored
1262+ mouseRelease(draggedItem)
1263+ tryCompare(findChild(draggedItem, "dropIndicator"), "opacity", 0)
1264+ tryCompare(draggedItem, "itemOpacity", 1)
1265+ tryCompare(fakeDragItem, "visible", false)
1266+ }
1267+ }
1268 }

Subscribers

People subscribed via source and target branches