Merge lp:~mzanetti/unity8/dnd-and-quicklists into lp:unity8
- dnd-and-quicklists
- Merge into trunk
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 |
Related bugs: |
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.
- 122. By Michael Zanetti
-
remove duplicate dummy items again
Michał Sawicz (saviq) wrote : | # |
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:122
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
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
Michael Zanetti (mzanetti) wrote : | # |
> Outputs:
>
> state changed to expanded
> state changed to
removed
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.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:123
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Michał Sawicz (saviq) wrote : | # |
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 LauncherListDel
=====
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
743 + NumberAnimation { properties: "angle,offset"; duration: UbuntuAnimation
749 + NumberAnimation { properties: "angle,offset"; duration: UbuntuAnimation
754 + NumberAnimation { properties: "height"; duration: UbuntuAnimation
760 + NumberAnimation { properties: "height"; duration: UbuntuAnimation
Use UbuntuNumberAni
=====
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
698 + }
Since there are states already, maybe move that there? UbuntuNumberAni
=====
729 + }
730 +
731 + ]
Drop newline.
=====
767 + anchors.fill: parent
768 + anchors.topMargin: launcherListVie
769 + anchors.
Try to remember and group those.
=====
778 + var realContentY = launcherListVie
785 + var realContentY = launcherListVie
873 + var realContentY = launcherListVie
Should take originY into account.
Maybe make it into a function to reuse? Same for realItemHeight?
=====
790 + if (index == 0 || index == launcherListVie
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?
===...
- 124. By Michael Zanetti
-
make folding after dragging sequential to expanding to avoid weird jumpiness
- 125. By Michael Zanetti
-
LauncherListDel
egate -> FoldingLauncher Delegate - 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 UbuntuNumberAni
mation 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
Michael Zanetti (mzanetti) wrote : | # |
> 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 LauncherListDel
> LauncherItem and LauncherDelegate, maybe?
renamed LauncherListDel
>
> =====
>
> 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
>
> 743 + NumberAnimation { properties:
> "angle,offset"; duration: UbuntuAnimation
> UbuntuAnimation
>
> 749 + NumberAnimation { properties:
> "angle,offset"; duration: UbuntuAnimation
> UbuntuAnimation
>
> 754 + NumberAnimation { properties: "height";
> duration: UbuntuAnimation
> }
>
> 760 + NumberAnimation { properties: "height";
> duration: UbuntuAnimation
> UbuntuAnimation
>
> Use UbuntuNumberAni
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
> 698 + }
>
> ...
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:126
http://
Executed test runs:
UNSTABLE: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
UNSTABLE: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:134
http://
Executed test runs:
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 135. By Michael Zanetti
-
take originY into account for progressiveScro
lling
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:135
http://
Executed test runs:
UNSTABLE: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
UNSTABLE: http://
Click here to trigger a rebuild:
http://
- 136. By Michael Zanetti
-
use new launcher background asset
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:136
http://
Executed test runs:
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 137. By Michael Zanetti
-
fix relasing before dragging, improve test
- 138. By Michael Zanetti
-
invert it again
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:136
http://
Executed test runs:
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:138
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
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.
- 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
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 progressiveScro
>
> 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.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:143
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Michał Sawicz (saviq) : | # |
Preview Diff
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' |
1016 | Binary 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' |
1028 | Binary 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 | } |
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...