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
=== renamed file 'Launcher/LauncherDelegate.qml' => 'Launcher/FoldingLauncherDelegate.qml'
--- Launcher/LauncherDelegate.qml 2013-07-02 19:19:46 +0000
+++ Launcher/FoldingLauncherDelegate.qml 2013-08-19 11:48:45 +0000
@@ -17,149 +17,112 @@
17import QtQuick 2.017import QtQuick 2.0
18import Ubuntu.Components 0.118import Ubuntu.Components 0.1
1919
20Item {20LauncherDelegate {
21 id: root21 id: root
2222
23 property string iconName23 // The angle used for rotating
2424 angle: {
25 property real angle: 025 // First/last items are special
26 property bool highlighted: false26 if (index == 0 || index == priv.listView.count-1) {
27 property real offset: 027 if (priv.distanceFromEdge < 0) {
28 property alias brightness: transformEffect.brightness28 // proportion equation: distanceFromTopEdge : angle = totalUnfoldedHeight/2 : maxAngle
29 property real itemOpacity: 129 return Math.max(-maxAngle, priv.distanceFromEdge * maxAngle / (priv.listView.foldingAreaHeight)) * priv.orientationFlag
3030 }
31 property real maxAngle: 031 return 0; // Don't fold first/last item as long as inside the view
32 property bool inverted: false32 }
3333
34 readonly property int effectiveHeight: Math.cos(angle * Math.PI / 180) * height34 // Are we in the already completely outside the flickable? Fold for the last 5 degrees
35 readonly property real foldedHeight: Math.cos(maxAngle * Math.PI / 180) * height35 if (priv.distanceFromEdge < 0) {
3636 // proportion equation: -distanceFromTopEdge : angle = totalUnfoldedHeight : 5
37 property bool dragging:false37 return Math.max(-maxAngle, (priv.distanceFromEdge * 5 / priv.listView.foldingAreaHeight) - (maxAngle-5)) * priv.orientationFlag
3838 }
39 signal clicked()39
40 signal longtap()40 // We are overlapping with the folding area, fold the icon to maxAngle - 5 degrees
41 signal released()41 if (priv.overlapWithFoldingArea > 0) {
4242 // proportion equation: overlap: totalHeight = angle : (maxAngle - 5)
43 Item {43 return -priv.overlapWithFoldingArea * (maxAngle -5) / priv.listView.foldingAreaHeight * priv.orientationFlag;
44 id: iconItem44 }
45 width: parent.width45 return 0;
46 height: parent.height46 }
47 anchors.centerIn: parent47
4848 // This is the offset that keeps the items inside the panel
49 UbuntuShape {49 offset: {
50 id: iconShape50 // First/last items are special
51 anchors.fill: parent51 if (index == 0 || index == priv.listView.count-1) {
52 anchors.margins: units.gu(0.5)52 // Just keep them bound to the edges in case they're outside of the visible area
53 radius: "medium"53 if (priv.distanceFromEdge < 0) {
5454 return (-priv.distanceFromEdge - (height - effectiveHeight)) * priv.orientationFlag;
55 image: Image {55 }
56 id: iconImage56 return 0;
57 sourceSize.width: iconShape.width57 }
58 sourceSize.height: iconShape.height58
59 source: "../graphics/applicationIcons/" + root.iconName + ".png"59 // Are we already completely outside the flickable? Stop the icon here.
60 }60 if (priv.distanceFromEdge < -priv.totalUnfoldedHeight) {
6161 return (-priv.distanceFromEdge - (root.height - effectiveHeight)) * priv.orientationFlag;
62 MouseArea {62 }
63 id: mouseArea63
64 anchors.fill: parent64 // We're touching the edge, move slower than the actual flicking speed.
65 onClicked: root.clicked()65 if (priv.distanceFromEdge < 0) {
66 onCanceled: root.released()66 return (Math.abs(priv.distanceFromEdge) * priv.totalEffectiveHeight / priv.totalUnfoldedHeight) * priv.orientationFlag
67 preventStealing: false67 }
6868 return 0;
69 onPressAndHold: {69 }
70 root.state = "moving"70
71 }71 itemOpacity: {
72 onReleased: {72 // First/last items are special
73 root.state = "docked"73 if (index == 0 || index == priv.listView.count-1) {
74 }74 if (priv.distanceFromEdge < 0) {
75 }75 // Fade from 1 to 0 in the distance of 3 * foldingAreaHeight (which is when the next item reaches the edge)
76 }76 return 1.0 - (-priv.distanceFromEdge / (priv.listView.foldingAreaHeight * 3))
77 BorderImage {77 }
78 id: overlayHighlight78 return 1; // Don't make first/last item transparent as long as inside the view
79 anchors.centerIn: iconItem79 }
80 rotation: inverted ? 180 : 080
81 source: isSelected ? "graphics/selected.sci" : "graphics/non-selected.sci"81 // Are we already completely outside the flickable? Fade from 0.75 to 0 in 2 items height
82 width: root.width + units.gu(0.5)82 if (priv.distanceFromEdge < 0) {
83 height: width83 // proportion equation: -distanceFromEdge : 1-opacity = totalUnfoldedHeight : 0.75
84 property bool isSelected: root.highlighted || mouseArea.pressed84 return 0.75 - (-priv.distanceFromEdge * 0.75 / (priv.totalUnfoldedHeight*2))
85 onIsSelectedChanged: shaderEffectSource.scheduleUpdate();85 }
86 }86
87 }87 // We are overlapping with the folding area, fade out to 0.75
8888 if (priv.overlapWithFoldingArea > 0) {
89 ShaderEffect {89 // proportion equation: overlap : totalHeight = 1-opacity : 0.25
90 id: transformEffect90 return 1 - (priv.overlapWithFoldingArea * 0.25 / priv.listView.foldingAreaHeight)
91 anchors.centerIn: parent91 }
92 anchors.verticalCenterOffset: root.offset92 return 1;
93 width: parent.width93 }
94 height: parent.height94
95 property real itemOpacity: root.itemOpacity95 brightness: {
96 property real brightness: 096 // First/last items are special
97 property real angle: root.angle97 if (index == 0 || index == priv.listView.count-1) {
98 rotation: root.inverted ? 180 : 098 if (priv.distanceFromEdge < 0) {
9999 return -(-priv.distanceFromEdge / (priv.listView.foldingAreaHeight * 3))
100100 }
101 property variant source: ShaderEffectSource {101 return 0;
102 id: shaderEffectSource102 }
103 sourceItem: iconItem103 // Are we already completely outside the flickable? Fade from 0.7 to 0 in 2 items height
104 hideSource: true104 if (priv.distanceFromEdge < 0) {
105 live: false105 return -0.3 - (-priv.distanceFromEdge * 0.1 / (priv.totalUnfoldedHeight*2))
106 }106 }
107107
108 transform: [108 // We are overlapping with the folding area, fade out to 0.7
109 // Rotating 3 times at top/bottom because that increases the perspective.109 if (priv.overlapWithFoldingArea > 0) {
110 // This is a hack, but as QML does not support real 3D coordinates110 return - (priv.overlapWithFoldingArea * 0.3 / priv.listView.foldingAreaHeight)
111 // getting a higher perspective can only be done by a hack. This is the most111 }
112 // readable/understandable one I could come up with.112 return 0;
113 Rotation {
114 axis { x: 1; y: 0; z: 0 }
115 origin { x: iconItem.width / 2; y: angle > 0 ? 0 : iconItem.height; z: 0 }
116 angle: root.angle * 0.7
117 },
118 Rotation {
119 axis { x: 1; y: 0; z: 0 }
120 origin { x: iconItem.width / 2; y: angle > 0 ? 0 : iconItem.height; z: 0 }
121 angle: root.angle * 0.7
122 },
123 Rotation {
124 axis { x: 1; y: 0; z: 0 }
125 origin { x: iconItem.width / 2; y: angle > 0 ? 0 : iconItem.height; z: 0 }
126 angle: root.angle * 0.7
127 },
128 // Because rotating it 3 times moves it more to the front/back, i.e. it gets
129 // bigger/smaller and we need a scale to compensate that again.
130 Scale {
131 xScale: 1 - (Math.abs(angle) / 500)
132 yScale: 1 - (Math.abs(angle) / 500)
133 origin { x: iconItem.width / 2; y: iconItem.height / 2}
134 }
135 ]
136
137 // Using a fragment shader instead of QML's opacity and BrightnessContrast
138 // to be able to do both in one step which gives quite some better performance
139 fragmentShader: "
140 varying highp vec2 qt_TexCoord0;
141 uniform sampler2D source;
142 uniform lowp float brightness;
143 uniform lowp float itemOpacity;
144 void main(void)
145 {
146 highp vec4 sourceColor = texture2D(source, qt_TexCoord0);
147 sourceColor.rgb = mix(sourceColor.rgb, vec3(step(0.0, brightness)), abs(brightness));
148 sourceColor *= itemOpacity;
149 gl_FragColor = sourceColor;
150 }"
151 }113 }
152114
153 QtObject {115 QtObject {
154 id: priv116 id: priv
155117
156 property ListView listView: root.ListView.view118 property ListView listView: root.ListView.view
157 property real totalUnfoldedHeight: listView.itemSize + listView.spacing119 property real totalUnfoldedHeight: listView.itemHeight + listView.spacing
158 property real totalEffectiveHeight: effectiveHeight + listView.spacing120 property real totalEffectiveHeight: effectiveHeight + listView.spacing
159 property real effectiveContentY: listView.contentY - listView.originY121 property real effectiveContentY: listView.contentY - listView.originY
160 property real effectiveY: y - listView.originY122 property real effectiveY: y - listView.originY
123 property real bottomDraggingDistanceOffset: listView.draggedIndex > index ? totalUnfoldedHeight : 0
161 property real distanceFromTopEdge: -(effectiveContentY + listView.topMargin - index*totalUnfoldedHeight)124 property real distanceFromTopEdge: -(effectiveContentY + listView.topMargin - index*totalUnfoldedHeight)
162 property real distanceFromBottomEdge: listView.height - listView.bottomMargin - (effectiveY+height) + effectiveContentY125 property real distanceFromBottomEdge: listView.height - listView.bottomMargin - (effectiveY+height) + effectiveContentY + bottomDraggingDistanceOffset
163126
164 property real distanceFromEdge: Math.abs(distanceFromBottomEdge) < Math.abs(distanceFromTopEdge) ? distanceFromBottomEdge : distanceFromTopEdge127 property real distanceFromEdge: Math.abs(distanceFromBottomEdge) < Math.abs(distanceFromTopEdge) ? distanceFromBottomEdge : distanceFromTopEdge
165 property real orientationFlag: Math.abs(distanceFromBottomEdge) < Math.abs(distanceFromTopEdge) ? -1 : 1128 property real orientationFlag: Math.abs(distanceFromBottomEdge) < Math.abs(distanceFromTopEdge) ? -1 : 1
@@ -167,123 +130,4 @@
167 property real overlapWithFoldingArea: listView.foldingAreaHeight - distanceFromEdge130 property real overlapWithFoldingArea: listView.foldingAreaHeight - distanceFromEdge
168 }131 }
169132
170 states: [
171 State {
172 name: "docked"
173 PropertyChanges {
174 target: root
175
176 // This is the offset that keeps the items inside the panel
177 offset: {
178 // First/last items are special
179 if (index == 0 || index == priv.listView.count-1) {
180 // Just keep them bound to the edges in case they're outside of the visible area
181 if (priv.distanceFromEdge < 0) {
182 return (-priv.distanceFromEdge - (height - effectiveHeight)) * priv.orientationFlag;
183 }
184 return 0;
185 }
186
187 // Are we already completely outside the flickable? Stop the icon here.
188 if (priv.distanceFromEdge < -priv.totalUnfoldedHeight) {
189 return (-priv.distanceFromEdge - (root.height - effectiveHeight)) * priv.orientationFlag;
190 }
191
192 // We're touching the edge, move slower than the actual flicking speed.
193 if (priv.distanceFromEdge < 0) {
194 return (Math.abs(priv.distanceFromEdge) * priv.totalEffectiveHeight / priv.totalUnfoldedHeight) * priv.orientationFlag
195 }
196 return 0;
197 }
198
199 // The angle used for rotating
200 angle: {
201 // First/last items are special
202 if (index == 0 || index == priv.listView.count-1) {
203 if (priv.distanceFromEdge < 0) {
204 // proportion equation: distanceFromTopEdge : angle = totalUnfoldedHeight/2 : maxAngle
205 return Math.max(-maxAngle, priv.distanceFromEdge * maxAngle / (priv.listView.foldingAreaHeight)) * priv.orientationFlag
206 }
207 return 0; // Don't fold first/last item as long as inside the view
208 }
209
210 // Are we in the already completely outside the flickable? Fold for the last 5 degrees
211 if (priv.distanceFromEdge < 0) {
212 // proportion equation: -distanceFromTopEdge : angle = totalUnfoldedHeight : 5
213 return Math.max(-maxAngle, (priv.distanceFromEdge * 5 / priv.listView.foldingAreaHeight) - (maxAngle-5)) * priv.orientationFlag
214 }
215
216 // We are overlapping with the folding area, fold the icon to maxAngle - 5 degrees
217 if (priv.overlapWithFoldingArea > 0) {
218 // proportion equation: overlap: totalHeight = angle : (maxAngle - 5)
219 return -priv.overlapWithFoldingArea * (maxAngle -5) / priv.listView.foldingAreaHeight * priv.orientationFlag;
220 }
221 return 0;
222 }
223
224 itemOpacity: {
225 // First/last items are special
226 if (index == 0 || index == priv.listView.count-1) {
227 if (priv.distanceFromEdge < 0) {
228 // Fade from 1 to 0 in the distance of 3 * foldingAreaHeight (which is when the next item reaches the edge)
229 return 1.0 - (-priv.distanceFromEdge / (priv.listView.foldingAreaHeight * 3))
230 }
231 return 1; // Don't make first/last item transparent as long as inside the view
232 }
233
234 // Are we already completely outside the flickable? Fade from 0.75 to 0 in 2 items height
235 if (priv.distanceFromEdge < 0) {
236 // proportion equation: -distanceFromEdge : 1-opacity = totalUnfoldedHeight : 0.75
237 return 0.75 - (-priv.distanceFromEdge * 0.75 / (priv.totalUnfoldedHeight*2))
238 }
239
240 // We are overlapping with the folding area, fade out to 0.75
241 if (priv.overlapWithFoldingArea > 0) {
242 // proportion equation: overlap : totalHeight = 1-opacity : 0.25
243 return 1 - (priv.overlapWithFoldingArea * 0.25 / priv.listView.foldingAreaHeight)
244 }
245 return 1;
246 }
247
248 brightness: {
249 // First/last items are special
250 if (index == 0 || index == priv.listView.count-1) {
251 if (priv.distanceFromEdge < 0) {
252 return -(-priv.distanceFromEdge / (priv.listView.foldingAreaHeight * 3))
253 }
254 return 0;
255 }
256 // Are we already completely outside the flickable? Fade from 0.7 to 0 in 2 items height
257 if (priv.distanceFromEdge < 0) {
258 return -0.3 - (-priv.distanceFromEdge * 0.1 / (priv.totalUnfoldedHeight*2))
259 }
260
261 // We are overlapping with the folding area, fade out to 0.7
262 if (priv.overlapWithFoldingArea > 0) {
263 return - (priv.overlapWithFoldingArea * 0.3 / priv.listView.foldingAreaHeight)
264 }
265 return 0;
266 }
267 }
268 },
269
270 State {
271 name: "moving"
272 PropertyChanges {
273 target: launcherDelegate;
274 offset: 0
275 angle: 0
276 }
277 PropertyChanges {
278 target: root
279 highlighted: true
280 dragging: true
281 }
282 PropertyChanges {
283 target: mouseArea
284 preventStealing: true
285 drag.target: root
286 }
287 }
288 ]
289}133}
290134
=== modified file 'Launcher/Launcher.qml'
--- Launcher/Launcher.qml 2013-07-05 11:34:14 +0000
+++ Launcher/Launcher.qml 2013-08-19 11:48:45 +0000
@@ -135,9 +135,9 @@
135 id: backgroundShade135 id: backgroundShade
136 anchors.fill: parent136 anchors.fill: parent
137 color: "black"137 color: "black"
138 opacity: root.state == "visible" ? 0.4 : 0138 opacity: root.state == "visible" ? 0.6 : 0
139139
140 Behavior on opacity { NumberAnimation { duration: 200; easing.type: Easing.OutQuad} }140 Behavior on opacity { NumberAnimation { duration: UbuntuAnimation.BriskDuration } }
141 }141 }
142142
143 LauncherPanel {143 LauncherPanel {
144144
=== added file 'Launcher/LauncherDelegate.qml'
--- Launcher/LauncherDelegate.qml 1970-01-01 00:00:00 +0000
+++ Launcher/LauncherDelegate.qml 2013-08-19 11:48:45 +0000
@@ -0,0 +1,137 @@
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 */
16
17import QtQuick 2.0
18import Ubuntu.Components 0.1
19
20Item {
21 id: root
22
23 property string iconName
24
25 property bool highlighted: false
26 property real maxAngle: 0
27 property bool inverted: false
28
29 readonly property int effectiveHeight: Math.cos(angle * Math.PI / 180) * itemHeight
30 readonly property real foldedHeight: Math.cos(maxAngle * Math.PI / 180) * itemHeight
31
32 property int itemWidth
33 property int itemHeight
34 // The angle used for rotating
35 property real angle: 0
36 // This is the offset that keeps the items inside the panel
37 property real offset: 0
38 property real itemOpacity: 1
39 property real brightness: 0
40
41 Item {
42 id: iconItem
43 width: parent.itemWidth
44 height: parent.itemHeight
45 anchors.centerIn: parent
46
47 UbuntuShape {
48 id: iconShape
49 anchors.fill: parent
50 anchors.margins: units.gu(0.5)
51 radius: "medium"
52
53 image: Image {
54 id: iconImage
55 sourceSize.width: iconShape.width
56 sourceSize.height: iconShape.height
57 source: "../graphics/applicationIcons/" + iconName + ".png"
58 property string iconName: root.iconName
59 onIconNameChanged: shaderEffectSource.scheduleUpdate();
60 }
61 }
62
63 BorderImage {
64 id: overlayHighlight
65 anchors.centerIn: iconItem
66 rotation: inverted ? 180 : 0
67 source: isSelected ? "graphics/selected.sci" : "graphics/non-selected.sci"
68 width: root.itemWidth + units.gu(0.5)
69 height: root.itemHeight + units.gu(0.5)
70 property bool isSelected: root.highlighted
71 onIsSelectedChanged: shaderEffectSource.scheduleUpdate();
72 }
73 }
74
75 ShaderEffect {
76 id: transformEffect
77 anchors.centerIn: parent
78 anchors.verticalCenterOffset: root.offset
79 width: parent.itemWidth
80 height: parent.itemHeight
81 property real itemOpacity: root.itemOpacity
82 property real brightness: Math.max(-1, root.brightness)
83 property real angle: root.angle
84 rotation: root.inverted ? 180 : 0
85
86 property variant source: ShaderEffectSource {
87 id: shaderEffectSource
88 sourceItem: iconItem
89 hideSource: true
90 live: false
91 }
92
93 transform: [
94 // Rotating 3 times at top/bottom because that increases the perspective.
95 // This is a hack, but as QML does not support real 3D coordinates
96 // getting a higher perspective can only be done by a hack. This is the most
97 // readable/understandable one I could come up with.
98 Rotation {
99 axis { x: 1; y: 0; z: 0 }
100 origin { x: iconItem.width / 2; y: angle > 0 ? 0 : iconItem.height; z: 0 }
101 angle: root.angle * 0.7
102 },
103 Rotation {
104 axis { x: 1; y: 0; z: 0 }
105 origin { x: iconItem.width / 2; y: angle > 0 ? 0 : iconItem.height; z: 0 }
106 angle: root.angle * 0.7
107 },
108 Rotation {
109 axis { x: 1; y: 0; z: 0 }
110 origin { x: iconItem.width / 2; y: angle > 0 ? 0 : iconItem.height; z: 0 }
111 angle: root.angle * 0.7
112 },
113 // Because rotating it 3 times moves it more to the front/back, i.e. it gets
114 // bigger/smaller and we need a scale to compensate that again.
115 Scale {
116 xScale: 1 - (Math.abs(angle) / 500)
117 yScale: 1 - (Math.abs(angle) / 500)
118 origin { x: iconItem.width / 2; y: iconItem.height / 2}
119 }
120 ]
121
122 // Using a fragment shader instead of QML's opacity and BrightnessContrast
123 // to be able to do both in one step which gives quite some better performance
124 fragmentShader: "
125 varying highp vec2 qt_TexCoord0;
126 uniform sampler2D source;
127 uniform lowp float brightness;
128 uniform lowp float itemOpacity;
129 void main(void)
130 {
131 highp vec4 sourceColor = texture2D(source, qt_TexCoord0);
132 sourceColor.rgb = mix(sourceColor.rgb, vec3(step(0.0, brightness)), abs(brightness));
133 sourceColor *= itemOpacity;
134 gl_FragColor = sourceColor;
135 }"
136 }
137}
0138
=== modified file 'Launcher/LauncherPanel.qml'
--- Launcher/LauncherPanel.qml 2013-07-11 08:19:10 +0000
+++ Launcher/LauncherPanel.qml 2013-08-19 11:48:45 +0000
@@ -16,6 +16,7 @@
1616
17import QtQuick 2.017import QtQuick 2.0
18import Ubuntu.Components 0.118import Ubuntu.Components 0.1
19import Ubuntu.Components.ListItems 0.1 as ListItems
19import Unity 0.120import Unity 0.1
20import Unity.Launcher 0.121import Unity.Launcher 0.1
21import "../Components/ListItems"22import "../Components/ListItems"
@@ -28,54 +29,33 @@
28 property var model29 property var model
29 property bool inverted: true30 property bool inverted: true
30 property bool dragging: false31 property bool dragging: false
31 property bool moving: launcherListView.moving || launcherListView.flicking32 property bool moving: launcherListView.moving || launcherListView.flicking || dndArea.draggedIndex >= 0
32 property int dragPosition: 0
33 property int highlightIndex: -133 property int highlightIndex: -1
3434
35 signal applicationSelected(string desktopFile)35 signal applicationSelected(string desktopFile)
36 signal dashItemSelected(int index)36 signal dashItemSelected(int index)
3737
38 onDragPositionChanged: {
39 var effectiveDragPosition = root.inverted ? launcherListView.height - dragPosition : dragPosition - mainColumn.anchors.margins
40
41 var hiddenContentHeight = launcherListView.contentHeight - launcherListView.height
42 // Shortening scrollable height because the first/last item needs to be fully expanded before reaching the top/bottom
43 var scrollableHeight = launcherListView.height - (launcherListView.itemSize + launcherColumn.spacing) *2
44 // As we shortened the scrollableHeight, lets move everything down by the itemSize
45 var shortenedEffectiveDragPosition = effectiveDragPosition - launcherListView.itemSize - launcherColumn.spacing
46 var newContentY = shortenedEffectiveDragPosition * hiddenContentHeight / scrollableHeight
47
48 // limit top/bottom to prevent overshooting
49 launcherListView.contentY = Math.min(hiddenContentHeight, Math.max(0, newContentY));
50
51 // Now calculate the current index:
52 // > the current mouse position + the hidden/scolled content on top is the mouse position in the averall view
53 // > adjust that removing all the margins
54 // > divide by itemSize to get index
55 highlightIndex = (effectiveDragPosition + launcherListView.contentY - mainColumn.anchors.margins*3 - launcherColumn.spacing/2) / (launcherListView.itemSize + launcherColumn.spacing)
56 }
57
58 BorderImage {38 BorderImage {
59 id: background39 id: background
60 source: "graphics/launcher_bg.sci"40 source: "graphics/launcher_bg.sci"
61 anchors.fill: parent41 anchors.fill: parent
42 anchors.rightMargin: root.inverted ? 0 : -units.gu(1)
43 anchors.leftMargin: root.inverted ? -units.gu(1) : 0
44 rotation: root.rotation
62 }45 }
6346
64 Column {47 Column {
65 id: mainColumn48 id: mainColumn
66 anchors {49 anchors {
67 fill: parent50 fill: parent
68 topMargin: units.gu(0.5)
69 bottomMargin: units.gu(1)
70 leftMargin: units.gu(0.5)51 leftMargin: units.gu(0.5)
71 rightMargin: units.gu(0.5)52 rightMargin: units.gu(0.5)
72 }53 }
73 spacing: units.gu(0.5)
7454
75 MouseArea {55 MouseArea {
76 id: dashItem56 id: dashItem
77 width: parent.width57 width: parent.width
78 height: units.gu(6.5)58 height: units.gu(7)
79 onClicked: root.dashItemSelected(0)59 onClicked: root.dashItemSelected(0)
80 z: 160 z: 1
81 Image {61 Image {
@@ -100,87 +80,336 @@
100 anchors.left: parent.left80 anchors.left: parent.left
101 anchors.right: parent.right81 anchors.right: parent.right
102 height: parent.height - dashItem.height - parent.spacing*282 height: parent.height - dashItem.height - parent.spacing*2
103 ListView {83
104 id: launcherListView84 Item {
105 objectName: "launcherListView"
106 anchors.fill: parent85 anchors.fill: parent
107 anchors.topMargin: -itemSize86 clip: true
108 anchors.bottomMargin: -itemSize87
109 topMargin: itemSize88 ListView {
110 bottomMargin: itemSize89 id: launcherListView
111 height: parent.height - dashItem.height - parent.spacing*290 objectName: "launcherListView"
112 model: root.model91 anchors {
113 cacheBuffer: itemSize * 392 fill: parent
114 snapMode: ListView.SnapToItem93 topMargin: -extensionSize + units.gu(0.5)
115 highlightRangeMode: ListView.ApplyRange94 bottomMargin: -extensionSize + units.gu(1)
116 preferredHighlightBegin: (height - itemSize) / 295 }
117 preferredHighlightEnd: (height + itemSize) / 296 topMargin: extensionSize
11897 bottomMargin: extensionSize
119 // The height of the area where icons start getting folded98 height: parent.height - dashItem.height - parent.spacing*2
120 property int foldingAreaHeight: itemSize * 0.7599 model: root.model
121 property int itemSize: width100 cacheBuffer: itemHeight * 3
122 property int clickFlickSpeed: units.gu(60)101 snapMode: interactive ? ListView.SnapToItem : ListView.NoSnap
123102 highlightRangeMode: ListView.ApplyRange
124 delegate: LauncherDelegate {103 preferredHighlightBegin: (height - itemHeight) / 2
125 id: launcherDelegate104 preferredHighlightEnd: (height + itemHeight) / 2
126 objectName: "launcherDelegate" + index105 spacing: units.gu(0.5)
127 width: launcherListView.itemSize106
128 height: launcherListView.itemSize107 // The size of the area the ListView is extended to make sure items are not
129 iconName: model.icon108 // destroyed when dragging them outside the list. This needs to be at least
130 inverted: root.inverted109 // itemHeight to prevent folded items from disappearing and DragArea limits
131 highlighted: root.dragging && index === root.highlightIndex110 // need to be smaller than this size to avoid breakage.
132 z: -Math.abs(offset)111 property int extensionSize: itemHeight * 3
133 state: "docked"112
134 maxAngle: 60113 // The height of the area where icons start getting folded
135114 property int foldingAreaHeight: itemHeight * 0.75
136 onClicked: {115 property int itemWidth: width
137 // First/last item do the scrolling at more than 12 degrees116 property int itemHeight: width * 7.5 / 8
138 if (index == 0 || index == launcherListView.count -1) {117 property int clickFlickSpeed: units.gu(60)
139 if (angle > 12) {118 property int draggedIndex: dndArea.draggedIndex
119 property real realContentY: contentY - originY + topMargin
120 property int realItemHeight: itemHeight + spacing
121
122 displaced: Transition {
123 NumberAnimation { properties: "x,y"; duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing }
124 }
125
126 delegate: FoldingLauncherDelegate {
127 id: launcherDelegate
128 objectName: "launcherDelegate" + index
129 itemHeight: launcherListView.itemHeight
130 itemWidth: launcherListView.itemWidth
131 width: itemWidth
132 height: itemHeight
133 iconName: model.icon
134 inverted: root.inverted
135 highlighted: dragging && index === root.highlightIndex
136 z: -Math.abs(offset)
137 maxAngle: 60
138 property bool dragging: false
139
140 ThinDivider {
141 id: dropIndicator
142 objectName: "dropIndicator"
143 anchors.centerIn: parent
144 width: parent.width + mainColumn.anchors.leftMargin + mainColumn.anchors.rightMargin
145 opacity: 0
146 }
147
148 states: [
149 State {
150 name: "selected"
151 when: dndArea.selectedItem === launcherDelegate && fakeDragItem.visible && !dragging
152 PropertyChanges {
153 target: launcherDelegate
154 itemOpacity: 0
155 }
156 },
157 State {
158 name: "dragging"
159 when: dragging
160 PropertyChanges {
161 target: launcherDelegate
162 height: units.gu(1)
163 itemOpacity: 0
164 }
165 PropertyChanges {
166 target: dropIndicator
167 opacity: 1
168 }
169 },
170 State {
171 name: "expanded"
172 when: dndArea.draggedIndex >= 0 && (dndArea.preDragging || dndArea.dragging || dndArea.postDragging) && dndArea.draggedIndex != index
173 PropertyChanges {
174 target: launcherDelegate
175 angle: 0
176 offset: 0
177 itemOpacity: 0.6
178 }
179 }
180 ]
181
182 transitions: [
183 Transition {
184 from: ""
185 to: "selected"
186 NumberAnimation { properties: "itemOpacity"; duration: UbuntuAnimation.FastDuration }
187 },
188 Transition {
189 from: "*"
190 to: "expanded"
191 NumberAnimation { properties: "itemOpacity"; duration: UbuntuAnimation.FastDuration }
192 UbuntuNumberAnimation { properties: "angle,offset" }
193 },
194 Transition {
195 from: "expanded"
196 to: ""
197 NumberAnimation { properties: "itemOpacity"; duration: UbuntuAnimation.BriskDuration }
198 UbuntuNumberAnimation { properties: "angle,offset" }
199 },
200 Transition {
201 from: "selected"
202 to: "dragging"
203 UbuntuNumberAnimation { properties: "height" }
204 NumberAnimation { target: dropIndicator; properties: "opacity"; duration: UbuntuAnimation.FastDuration }
205 },
206 Transition {
207 from: "dragging"
208 to: "*"
209 NumberAnimation { target: dropIndicator; properties: "opacity"; duration: UbuntuAnimation.FastDuration }
210 NumberAnimation { properties: "itemOpacity"; duration: UbuntuAnimation.BriskDuration }
211 SequentialAnimation {
212 UbuntuNumberAnimation { properties: "height" }
213 PropertyAction { target: dndArea; property: "postDragging"; value: false }
214 PropertyAction { target: dndArea; property: "draggedIndex"; value: -1 }
215 }
216 }
217 ]
218 }
219
220 MouseArea {
221 id: dndArea
222 anchors {
223 fill: parent
224 topMargin: launcherListView.topMargin
225 bottomMargin: launcherListView.bottomMargin
226 }
227 drag.minimumY: -launcherListView.topMargin
228 drag.maximumY: height + launcherListView.bottomMargin
229
230 property int draggedIndex: -1
231 property var selectedItem
232 property bool preDragging: false
233 property bool dragging: selectedItem !== undefined && selectedItem.dragging
234 property bool postDragging: false
235 property int startX
236 property int startY
237
238 onPressed: {
239 selectedItem = launcherListView.itemAt(mouseX, mouseY + launcherListView.realContentY)
240 selectedItem.highlighted = true
241 }
242
243 onClicked: {
244 var index = Math.floor((mouseY + launcherListView.realContentY) / launcherListView.realItemHeight);
245 var clickedItem = launcherListView.itemAt(mouseX, mouseY + launcherListView.realContentY)
246
247 // First/last item do the scrolling at more than 12 degrees
248 if (index == 0 || index == launcherListView.count - 1) {
249 if (clickedItem.angle > 12) {
250 launcherListView.flick(0, -launcherListView.clickFlickSpeed);
251 } else if (clickedItem.angle < -12) {
252 launcherListView.flick(0, launcherListView.clickFlickSpeed);
253 } else {
254 root.applicationSelected(LauncherModel.get(index).desktopFile);
255 }
256 return;
257 }
258
259 // the rest launches apps up to an angle of 30 degrees
260 if (clickedItem.angle > 30) {
140 launcherListView.flick(0, -launcherListView.clickFlickSpeed);261 launcherListView.flick(0, -launcherListView.clickFlickSpeed);
141 } else if (angle < -12) {262 } else if (clickedItem.angle < -30) {
142 launcherListView.flick(0, launcherListView.clickFlickSpeed);263 launcherListView.flick(0, launcherListView.clickFlickSpeed);
143 } else {264 } else {
144 root.applicationSelected(LauncherModel.get(index).desktopFile);265 root.applicationSelected(LauncherModel.get(index).desktopFile);
145 }266 }
146 return;267 }
147 }268
148269 onCanceled: {
149 // the rest launches apps up to an angle of 30 degrees270 selectedItem.highlighted = false
150 if (angle > 30) {271 selectedItem = undefined;
151 launcherListView.flick(0, -launcherListView.clickFlickSpeed);272 preDragging = false;
152 } else if (angle < -30) {273 postDragging = false;
153 launcherListView.flick(0, launcherListView.clickFlickSpeed);274 }
154 } else {275
155 root.applicationSelected(LauncherModel.get(index).desktopFile);276 onReleased: {
156 }277 var droppedIndex = draggedIndex;
157 }278 if (dragging) {
158 }279 postDragging = true;
159280 } else {
160 MouseArea {281 draggedIndex = -1;
161 id: topFoldingArea282 }
162 anchors {283
163 left: parent.left284 selectedItem.highlighted = false
164 right: parent.right285 selectedItem.dragging = false;
165 top: parent.top286 selectedItem = undefined;
166 topMargin: launcherListView.topMargin287 preDragging = false;
167 }288
168 height: launcherListView.itemSize / 2289 drag.target = undefined
169 enabled: launcherListView.contentY > -launcherListView.topMargin290
170 onClicked: launcherListView.flick(0, launcherListView.clickFlickSpeed)291 progressiveScrollingTimer.stop();
171 }292 launcherListView.interactive = true;
172293 if (droppedIndex >= launcherListView.count - 2 && postDragging) {
173 MouseArea {294 launcherListView.flick(0, -launcherListView.clickFlickSpeed);
174 id: bottomFoldingArea295 }
175 anchors {296 }
176 left: parent.left297
177 right: parent.right298 onPressAndHold: {
178 bottom: parent.bottom299 if (Math.abs(selectedItem.angle) > 30) {
179 bottomMargin: launcherListView.bottomMargin300 return;
180 }301 }
181 height: launcherListView.itemSize / 2302
182 enabled: launcherListView.contentHeight - launcherListView.height - launcherListView.contentY > -launcherListView.bottomMargin303 draggedIndex = Math.floor((mouseY + launcherListView.realContentY) / launcherListView.realItemHeight);
183 onClicked: launcherListView.flick(0, -launcherListView.clickFlickSpeed)304
305 launcherListView.interactive = false
306
307 var yOffset = draggedIndex > 0 ? (mouseY + launcherListView.realContentY) % (draggedIndex * launcherListView.realItemHeight) : mouseY + launcherListView.realContentY
308
309 fakeDragItem.iconName = launcherListView.model.get(draggedIndex).icon
310 fakeDragItem.x = 0
311 fakeDragItem.y = mouseY - yOffset + launcherListView.anchors.topMargin + launcherListView.topMargin
312 fakeDragItem.angle = selectedItem.angle * (root.inverted ? -1 : 1)
313 fakeDragItem.offset = selectedItem.offset * (root.inverted ? -1 : 1)
314 fakeDragItem.flatten()
315 drag.target = fakeDragItem
316
317 startX = mouseX
318 startY = mouseY
319 }
320
321 onPositionChanged: {
322 if (draggedIndex >= 0) {
323 if (!selectedItem.dragging) {
324 var distance = Math.max(Math.abs(mouseX - startX), Math.abs(mouseY - startY))
325 if (!preDragging && distance > units.gu(1.5)) {
326 preDragging = true;
327 }
328 if (distance > launcherListView.itemHeight) {
329 selectedItem.dragging = true
330 preDragging = false;
331 }
332 }
333 if (!selectedItem.dragging) {
334 return
335 }
336
337 var itemCenterY = fakeDragItem.y + fakeDragItem.height / 2
338
339 // Move it down by the the missing size to compensate index calculation with only expanded items
340 itemCenterY += (launcherListView.itemHeight - selectedItem.height) / 2
341
342 if (mouseY > launcherListView.height - launcherListView.topMargin - launcherListView.bottomMargin - launcherListView.realItemHeight) {
343 progressiveScrollingTimer.downwards = false
344 progressiveScrollingTimer.start()
345 } else if (mouseY < launcherListView.realItemHeight) {
346 progressiveScrollingTimer.downwards = true
347 progressiveScrollingTimer.start()
348 } else {
349 progressiveScrollingTimer.stop()
350 }
351
352 var newIndex = (itemCenterY + launcherListView.realContentY) / launcherListView.realItemHeight
353
354 if (newIndex > draggedIndex + 1) {
355 newIndex = draggedIndex + 1
356 } else if (newIndex < draggedIndex) {
357 newIndex = draggedIndex -1
358 } else {
359 return
360 }
361
362 if (newIndex >= 0 && newIndex < launcherListView.count) {
363 launcherListView.model.move(draggedIndex, newIndex)
364 draggedIndex = newIndex
365 }
366 }
367 }
368 }
369 Timer {
370 id: progressiveScrollingTimer
371 interval: 2
372 repeat: true
373 running: false
374 property bool downwards: true
375 onTriggered: {
376 if (downwards) {
377 var minY = -launcherListView.topMargin
378 if (launcherListView.contentY > minY) {
379 launcherListView.contentY = Math.max(launcherListView.contentY - units.dp(2), minY)
380 }
381 } else {
382 var maxY = launcherListView.contentHeight - launcherListView.height + launcherListView.topMargin + launcherListView.originY
383 if (launcherListView.contentY < maxY) {
384 launcherListView.contentY = Math.min(launcherListView.contentY + units.dp(2), maxY)
385 }
386 }
387 }
388 }
389 }
390 }
391
392 LauncherDelegate {
393 id: fakeDragItem
394 objectName: "fakeDragItem"
395 visible: dndArea.draggedIndex >= 0 && !dndArea.postDragging
396 itemWidth: launcherListView.itemWidth
397 itemHeight: launcherListView.itemHeight
398 height: itemHeight
399 width: itemWidth
400 rotation: root.rotation
401 highlighted: true
402 itemOpacity: 0.8
403
404 function flatten() {
405 fakeDragItemAnimation.start();
406 }
407
408 UbuntuNumberAnimation {
409 id: fakeDragItemAnimation
410 target: fakeDragItem;
411 properties: "angle,offset";
412 to: 0
184 }413 }
185 }414 }
186 }415 }
187416
=== removed file 'Launcher/graphics/launcher_bg@18.png'
188Binary 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 differ417Binary 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
=== removed file 'Launcher/graphics/launcher_bg@18.sci'
--- Launcher/graphics/launcher_bg@18.sci 2013-06-05 22:03:08 +0000
+++ Launcher/graphics/launcher_bg@18.sci 1970-01-01 00:00:00 +0000
@@ -1,5 +0,0 @@
1border.left: 0
2border.top: 0
3border.bottom: 0
4border.right: 24
5source: launcher_bg@18.png
60
=== added file 'Launcher/graphics/launcher_bg@30.png'
7Binary 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 differ1Binary 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
=== added file 'Launcher/graphics/launcher_bg@30.sci'
--- Launcher/graphics/launcher_bg@30.sci 1970-01-01 00:00:00 +0000
+++ Launcher/graphics/launcher_bg@30.sci 2013-08-19 11:48:45 +0000
@@ -0,0 +1,5 @@
1border.left: 4
2border.top: 4
3border.bottom: 4
4border.right: 66
5source: launcher_bg@30.png
06
=== modified file 'plugins/Unity/Launcher/backend/launcherbackend.cpp'
--- plugins/Unity/Launcher/backend/launcherbackend.cpp 2013-07-16 20:45:13 +0000
+++ plugins/Unity/Launcher/backend/launcherbackend.cpp 2013-08-19 11:48:45 +0000
@@ -101,7 +101,7 @@
101{101{
102 // TODO: return app's pinned state from settings102 // TODO: return app's pinned state from settings
103 Q_UNUSED(appId)103 Q_UNUSED(appId)
104 return true;104 return false;
105}105}
106106
107void LauncherBackend::setPinned(const QString &appId, bool pinned)107void LauncherBackend::setPinned(const QString &appId, bool pinned)
108108
=== modified file 'plugins/Unity/Launcher/launcheritem.cpp'
--- plugins/Unity/Launcher/launcheritem.cpp 2013-07-08 13:55:07 +0000
+++ plugins/Unity/Launcher/launcheritem.cpp 2013-08-19 11:48:45 +0000
@@ -33,7 +33,10 @@
33 m_count(0),33 m_count(0),
34 m_quickList(new QuickListModel(this))34 m_quickList(new QuickListModel(this))
35{35{
3636 QuickListEntry pinningAction;
37 pinningAction.setActionId("pin_item");
38 pinningAction.setText("Pin to Launcher");
39 m_quickList->appendAction(pinningAction);
37}40}
3841
39QString LauncherItem::appId() const42QString LauncherItem::appId() const
@@ -65,6 +68,10 @@
65{68{
66 if (m_pinned != pinned) {69 if (m_pinned != pinned) {
67 m_pinned = pinned;70 m_pinned = pinned;
71 QuickListEntry entry;
72 entry.setActionId("pin_item");
73 entry.setText(pinned ? "Remove from Launcher": "Pin to Launcher");
74 m_quickList->updateAction(entry);
68 Q_EMIT pinnedChanged(pinned);75 Q_EMIT pinnedChanged(pinned);
69 }76 }
70}77}
7178
=== modified file 'plugins/Unity/Launcher/launchermodel.cpp'
--- plugins/Unity/Launcher/launchermodel.cpp 2013-07-08 17:01:49 +0000
+++ plugins/Unity/Launcher/launchermodel.cpp 2013-08-19 11:48:45 +0000
@@ -82,8 +82,25 @@
8282
83void LauncherModel::move(int oldIndex, int newIndex)83void LauncherModel::move(int oldIndex, int newIndex)
84{84{
85 // Perform the move in our list85 // Make sure its not moved outside the lists
86 beginMoveRows(QModelIndex(), oldIndex, oldIndex, QModelIndex(), newIndex);86 if (newIndex < 0) {
87 newIndex = 0;
88 }
89 if (newIndex >= m_list.count()) {
90 newIndex = m_list.count()-1;
91 }
92
93 // Nothing to do?
94 if (oldIndex == newIndex) {
95 return;
96 }
97
98 // QList's and QAbstractItemModel's move implementation differ when moving an item up the list :/
99 // While QList needs the index in the resulting list, beginMoveRows expects it to be in the current list
100 // adjust the model's index by +1 in case we're moving upwards
101 int newModelIndex = newIndex > oldIndex ? newIndex+1 : newIndex;
102
103 beginMoveRows(QModelIndex(), oldIndex, oldIndex, QModelIndex(), newModelIndex);
87 m_list.move(oldIndex, newIndex);104 m_list.move(oldIndex, newIndex);
88 endMoveRows();105 endMoveRows();
89106
@@ -140,10 +157,23 @@
140 return;157 return;
141 }158 }
142159
143 QuickListModel *model = qobject_cast<QuickListModel*>(m_list.at(index)->quickList());160 LauncherItem *item = m_list.at(index);
161 QuickListModel *model = qobject_cast<QuickListModel*>(item->quickList());
144 if (model) {162 if (model) {
145 QString actionId = model->get(actionIndex).actionId();163 QString actionId = model->get(actionIndex).actionId();
146 m_backend->triggerQuickListAction(appId, actionId);164
165 // Check if this is one of the launcher actions we handle ourselves
166 if (actionId == "pin_item") {
167 if (item->pinned()) {
168 requestRemove(appId);
169 } else {
170 pin(appId);
171 }
172
173 // Nope, we don't know this action, let the backend forward it to the application
174 } else {
175 m_backend->triggerQuickListAction(appId, actionId);
176 }
147 }177 }
148}178}
149179
150180
=== modified file 'plugins/Unity/Launcher/plugin.cpp'
--- plugins/Unity/Launcher/plugin.cpp 2013-07-08 17:00:27 +0000
+++ plugins/Unity/Launcher/plugin.cpp 2013-08-19 11:48:45 +0000
@@ -41,7 +41,9 @@
4141
42 qmlRegisterUncreatableType<LauncherModelInterface>(uri, 0, 1, "LauncherModelInterface", "Abstract Interface. Cannot be instantiated.");42 qmlRegisterUncreatableType<LauncherModelInterface>(uri, 0, 1, "LauncherModelInterface", "Abstract Interface. Cannot be instantiated.");
43 qmlRegisterUncreatableType<LauncherItemInterface>(uri, 0, 1, "LauncherItemInterface", "Abstract Interface. Cannot be instantiated.");43 qmlRegisterUncreatableType<LauncherItemInterface>(uri, 0, 1, "LauncherItemInterface", "Abstract Interface. Cannot be instantiated.");
44 qmlRegisterUncreatableType<QuickListModelInterface>(uri, 0, 1, "QuickListInterface", "Abstract Interface. Cannot be instantiated.");
4445
45 qmlRegisterSingletonType<LauncherModel>(uri, 0, 1, "LauncherModel", modelProvider);46 qmlRegisterSingletonType<LauncherModel>(uri, 0, 1, "LauncherModel", modelProvider);
46 qmlRegisterUncreatableType<LauncherItem>(uri, 0, 1, "LauncherItem", "Can't create new Launcher Items in QML. Get them from the LauncherModel.");47 qmlRegisterUncreatableType<LauncherItem>(uri, 0, 1, "LauncherItem", "Can't create new Launcher Items in QML. Get them from the LauncherModel.");
48 qmlRegisterUncreatableType<QuickListModel>(uri, 0, 1, "QuickListModel", "Can't create a QuickListModel in QML. Get them from the LauncherItems.");
47}49}
4850
=== modified file 'plugins/Unity/Launcher/quicklistmodel.cpp'
--- plugins/Unity/Launcher/quicklistmodel.cpp 2013-07-08 13:55:07 +0000
+++ plugins/Unity/Launcher/quicklistmodel.cpp 2013-08-19 11:48:45 +0000
@@ -37,6 +37,17 @@
37 endInsertRows();37 endInsertRows();
38}38}
3939
40void QuickListModel::updateAction(const QuickListEntry &entry)
41{
42 for (int i = 0; i < m_list.count(); ++i) {
43 if (m_list.at(i).actionId() == entry.actionId()) {
44 m_list.replace(i, entry);
45 Q_EMIT dataChanged(index(i), index(i));
46 return;
47 }
48 }
49}
50
40QuickListEntry QuickListModel::get(int index) const51QuickListEntry QuickListModel::get(int index) const
41{52{
42 return m_list.at(index);53 return m_list.at(index);
4354
=== modified file 'plugins/Unity/Launcher/quicklistmodel.h'
--- plugins/Unity/Launcher/quicklistmodel.h 2013-07-08 13:55:07 +0000
+++ plugins/Unity/Launcher/quicklistmodel.h 2013-08-19 11:48:45 +0000
@@ -36,6 +36,16 @@
36 ~QuickListModel();36 ~QuickListModel();
3737
38 void appendAction(const QuickListEntry &entry);38 void appendAction(const QuickListEntry &entry);
39
40 /**
41 * @brief Update an existing action
42 * @param entry The new, updated entry
43 *
44 * This will only do something if entry.actionId is found in the model.
45 * To add a new entry, use appendAction().
46 */
47 void updateAction(const QuickListEntry &entry);
48
39 QuickListEntry get(int index) const;49 QuickListEntry get(int index) const;
4050
41 int rowCount(const QModelIndex &parent = QModelIndex()) const;51 int rowCount(const QModelIndex &parent = QModelIndex()) const;
4252
=== modified file 'tests/qmltests/Launcher/tst_Launcher.qml'
--- tests/qmltests/Launcher/tst_Launcher.qml 2013-07-05 09:02:07 +0000
+++ tests/qmltests/Launcher/tst_Launcher.qml 2013-08-19 11:48:45 +0000
@@ -19,6 +19,7 @@
19import Unity.Test 0.1 as UT19import Unity.Test 0.1 as UT
20import ".."20import ".."
21import "../../../Launcher"21import "../../../Launcher"
22import Unity.Launcher 0.1
2223
23/* Nothing is shown at first. If you drag from left edge you will bring up the24/* Nothing is shown at first. If you drag from left edge you will bring up the
24 launcher. */25 launcher. */
@@ -247,4 +248,63 @@
247 }248 }
248 */249 */
249 }250 }
251
252 UT.UnityTestCase {
253 id: dndTestCase
254 name: "Drag and Drop"
255 when: windowShown && initTestCase.completed
256
257 function test_dragndrop_data() {
258 return [
259 {tag: "startDrag", fullDrag: false},
260 {tag: "fullDrag", fullDrag: true},
261 ];
262 }
263
264 function test_dragndrop(data) {
265 revealer.dragLauncherIntoView();
266 var draggedItem = findChild(launcher, "launcherDelegate5")
267 var item0 = findChild(launcher, "launcherDelegate0")
268 var fakeDragItem = findChild(launcher, "fakeDragItem")
269 var initialItemHeight = draggedItem.height
270 var item5Name = LauncherModel.get(5).iconName
271 var item4Name = LauncherModel.get(4).iconName
272
273 // Initial state
274 compare(draggedItem.itemOpacity, 1, "Item's opacity is not 1 at beginning")
275 compare(fakeDragItem.visible, false, "FakeDragItem isn't invisible at the beginning")
276 tryCompare(findChild(draggedItem, "dropIndicator"), "opacity", 0)
277
278 // Doing longpress
279 mousePress(draggedItem, draggedItem.width / 2, draggedItem.height / 2)
280 // DraggedItem needs to hide and fakeDragItem become visible
281 tryCompare(draggedItem, "itemOpacity", 0)
282 tryCompare(fakeDragItem, "visible", true)
283
284 // Dragging a bit (> 1.5 gu)
285 mouseMove(draggedItem, -units.gu(2), draggedItem.height / 2)
286 // Other items need to expand and become 0.6 opaque
287 tryCompare(item0, "angle", 0)
288 tryCompare(item0, "itemOpacity", 0.6)
289
290 if (data.fullDrag) {
291 // Dragging a bit more
292 mouseMove(draggedItem, -units.gu(15), draggedItem.height / 2, 100)
293 tryCompare(findChild(draggedItem, "dropIndicator"), "opacity", 1)
294 tryCompare(draggedItem, "height", units.gu(1))
295
296 // Dragging downwards. Item needs to move in the model
297 mouseMove(draggedItem, -units.gu(15), -initialItemHeight)
298 waitForRendering(draggedItem)
299 compare(LauncherModel.get(5).iconName, item4Name)
300 compare(LauncherModel.get(5).iconName, item5Name)
301 }
302
303 // Releasing and checking if initial values are restored
304 mouseRelease(draggedItem)
305 tryCompare(findChild(draggedItem, "dropIndicator"), "opacity", 0)
306 tryCompare(draggedItem, "itemOpacity", 1)
307 tryCompare(fakeDragItem, "visible", false)
308 }
309 }
250}310}

Subscribers

People subscribed via source and target branches