Merge lp:~faenil/ubuntu-ui-toolkit/WIP_BROKEN_scrollableSectionsStyle into lp:ubuntu-ui-toolkit/staging
- WIP_BROKEN_scrollableSectionsStyle
- Merge into staging
| Status: | Rejected |
|---|---|
| Rejected by: | Andrea Bernabei on 2016-09-17 |
| Proposed branch: | lp:~faenil/ubuntu-ui-toolkit/WIP_BROKEN_scrollableSectionsStyle |
| Merge into: | lp:ubuntu-ui-toolkit/staging |
| Diff against target: |
639 lines (+534/-36) 2 files modified
examples/ubuntu-ui-toolkit-gallery/Sections.qml (+213/-8) modules/Ubuntu/Components/Themes/Ambiance/1.3/SectionsStyle.qml (+321/-28) |
| To merge this branch: | bzr merge lp:~faenil/ubuntu-ui-toolkit/WIP_BROKEN_scrollableSectionsStyle |
| Related bugs: |
| Reviewer | Review Type | Date Requested | Status |
|---|---|---|---|
| PS Jenkins bot | continuous-integration | Needs Fixing on 2015-07-13 | |
| Ubuntu SDK team | 2015-07-13 | Pending | |
|
Review via email:
|
|||
Commit Message
Description of the Change
This is a prototype WIP branch for an improved SectionsStyle.
It adds scrolling, keyboard navigation, highlight transition and automatic highlighted item centering.
It also shows clickable arrows on mouse hover. These arrows are meant as an alternative to scroll the bar when using pointer devices.
-------
The prototype currently uses ListView to implement the scrolling and keyboard navigation. Due to ListView's internal implementation though (which is heavily based on approximate calculations), I still have a few bugs which are triggered in some corner cases, for example when keeping the right arrow pressed in a listview which has many many delegates or with very different widths.
I am not sure those bugs are fixable at all keeping the current ListView implementation, so my plan is to move to Flickable+Repeater.
Keeping everything in memory in this case is a very acceptable compromise, and will give us peace of mind because contentWidth/
I wrote a small example which makes it easier to reproduce one of such bugs. You can find it in the UI Toolkit Gallery, in the Sections section.
- 1541. By Andrea Bernabei on 2015-07-13
-
delete commented state
| Andrea Bernabei (faenil) wrote : | # |
This was later implemented by Tim who completed this code
Unmerged revisions
- 1541. By Andrea Bernabei on 2015-07-13
-
delete commented state
- 1540. By Andrea Bernabei on 2015-07-13
-
merge staging
- 1539. By Andrea Bernabei on 2015-07-13
-
WIP PROTO SectionsStyle: add scrolling, hovering, keyboard navigation.
Preview Diff
| 1 | === modified file 'examples/ubuntu-ui-toolkit-gallery/Sections.qml' |
| 2 | --- examples/ubuntu-ui-toolkit-gallery/Sections.qml 2015-06-17 12:13:40 +0000 |
| 3 | +++ examples/ubuntu-ui-toolkit-gallery/Sections.qml 2015-07-13 16:58:44 +0000 |
| 4 | @@ -24,14 +24,219 @@ |
| 5 | title: "Sections" |
| 6 | className: "Sections" |
| 7 | |
| 8 | - TemplateRow { |
| 9 | - title: i18n.tr("Enabled") |
| 10 | - |
| 11 | - Sections { |
| 12 | - actions: [ |
| 13 | - Action { text: "one" }, |
| 14 | - Action { text: "two" }, |
| 15 | - Action { text: "three" } |
| 16 | + Label { |
| 17 | + anchors.left: parent.left |
| 18 | + anchors.right: parent.right |
| 19 | + wrapMode: Text.WrapAtWordBoundaryOrAnywhere |
| 20 | + text: "This is a special example which can be used to reproduce one of the bugs I have with the keyboard navigation in the Sections component. Just click on the first item, then press and hold the right arrow on your keyboard, then press and hold LEFT on your keyboard. You'll notice that the first (or last) element will align to the middle when it actually shouldn't. I believe this happens because contentWidth/X changes is changed on c++ side after I set it on qml side. This is just one of the bugs which happen as a consequence of us playing with contentX to scroll the view, because ListView doesn't provide a way to scroll a view WITH an animation. PositionViewAtIndex(..) is reliable BUT it does NOT animate, and it only works with an index." |
| 21 | + } |
| 22 | + |
| 23 | + TemplateRow { |
| 24 | + title: i18n.tr("Bug1") |
| 25 | + |
| 26 | + Sections { |
| 27 | + actions: [ |
| 28 | + Action { text: "two" }, |
| 29 | + Action { text: "oneoneoneoneoneoneoneoneoneoneoneo" }, |
| 30 | + Action { text: "oneoneoneoneoneoneoneoneoneoneoneon" }, |
| 31 | + Action { text: "oneoneoneoneoneoneoneoneoneoneoneon" }, |
| 32 | + Action { text: "oneoneoneoneoneoneoneoneoneoneoneone" }, |
| 33 | + Action { text: "two" } |
| 34 | + ] |
| 35 | + } |
| 36 | + } |
| 37 | + |
| 38 | + TemplateRow { |
| 39 | + title: i18n.tr("Bug1") |
| 40 | + |
| 41 | + Sections { |
| 42 | + actions: [ |
| 43 | + Action { text: "two" }, |
| 44 | + Action { text: "oneoneoneoneoneoneoneoneoneoneoneo" }, |
| 45 | + Action { text: "oneoneoneoneoneoneoneoneoneoneoneon" }, |
| 46 | + Action { text: "oneoneoneoneoneoneoneoneoneoneoneon" }, |
| 47 | + Action { text: "oneoneoneoneoneoneoneoneoneoneoneone" }, |
| 48 | + Action { text: "two" }, |
| 49 | + Action { text: "three" }, |
| 50 | + Action { text: "four" }, |
| 51 | + Action { text: "five" }, |
| 52 | + Action { text: "six" }, |
| 53 | + Action { text: "seven" }, |
| 54 | + Action { text: "sevenoneoneoneoneone" }, |
| 55 | + Action { text: "seven" }, |
| 56 | + Action { text: "seven" }, |
| 57 | + Action { text: "seven" }, |
| 58 | + Action { text: "seven" }, |
| 59 | + Action { text: "seven" }, |
| 60 | + Action { text: "seven" }, |
| 61 | + Action { text: "seven" }, |
| 62 | + Action { text: "seven" }, |
| 63 | + Action { text: "seven" }, |
| 64 | + Action { text: "seven" }, |
| 65 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 66 | + Action { text: "seven" }, |
| 67 | + Action { text: "seven" }, |
| 68 | + Action { text: "seven" }, |
| 69 | + Action { text: "seven" }, |
| 70 | + Action { text: "seven" }, |
| 71 | + Action { text: "seven" }, |
| 72 | + Action { text: "seven" }, |
| 73 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneonev" }, |
| 74 | + Action { text: "sevenoneoneoneoneoneoneoneoneone" }, |
| 75 | + Action { text: "seven" }, |
| 76 | + Action { text: "sevenoneoneoneoneoneone" }, |
| 77 | + Action { text: "seven" }, |
| 78 | + Action { text: "seven" }, |
| 79 | + Action { text: "seven" }, |
| 80 | + Action { text: "seven" }, |
| 81 | + Action { text: "seven" }, |
| 82 | + Action { text: "seven" }, |
| 83 | + Action { text: "seven" }, |
| 84 | + Action { text: "seven" }, |
| 85 | + Action { text: "seven" }, |
| 86 | + Action { text: "seven" }, |
| 87 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 88 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 89 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 90 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 91 | + Action { text: "seven" }, |
| 92 | + Action { text: "seven" }, |
| 93 | + Action { text: "seven" }, |
| 94 | + Action { text: "seven" }, |
| 95 | + Action { text: "seven" }, |
| 96 | + Action { text: "seven" }, |
| 97 | + Action { text: "seven" }, |
| 98 | + Action { text: "seven" }, |
| 99 | + Action { text: "seven" }, |
| 100 | + Action { text: "seven" }, |
| 101 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 102 | + Action { text: "seven" }, |
| 103 | + Action { text: "seven" }, |
| 104 | + Action { text: "seven" }, |
| 105 | + Action { text: "seven" }, |
| 106 | + Action { text: "seven" }, |
| 107 | + Action { text: "seven" }, |
| 108 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 109 | + Action { text: "seven" }, |
| 110 | + Action { text: "seven" }, |
| 111 | + Action { text: "seven" }, |
| 112 | + Action { text: "seven" }, |
| 113 | + Action { text: "seven" }, |
| 114 | + Action { text: "seven" }, |
| 115 | + Action { text: "seven" }, |
| 116 | + Action { text: "seven" }, |
| 117 | + Action { text: "sevenoneoneoneoneoneoneoneoneone" }, |
| 118 | + Action { text: "seven" }, |
| 119 | + Action { text: "seven" }, |
| 120 | + Action { text: "seven" }, |
| 121 | + Action { text: "seven" }, |
| 122 | + Action { text: "seven" }, |
| 123 | + Action { text: "seven" }, |
| 124 | + Action { text: "seven" }, |
| 125 | + Action { text: "seven" }, |
| 126 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 127 | + Action { text: "seven" }, |
| 128 | + Action { text: "seven" }, |
| 129 | + Action { text: "seven" }, |
| 130 | + Action { text: "sevenoneoneoneoneoneone" }, |
| 131 | + Action { text: "seven" }, |
| 132 | + Action { text: "seven" }, |
| 133 | + Action { text: "seven" }, |
| 134 | + Action { text: "seven" }, |
| 135 | + Action { text: "seven" }, |
| 136 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 137 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 138 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 139 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 140 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 141 | + Action { text: "seven" }, |
| 142 | + Action { text: "seven" }, |
| 143 | + Action { text: "seven" }, |
| 144 | + Action { text: "seven" }, |
| 145 | + Action { text: "seven" }, |
| 146 | + Action { text: "seven" }, |
| 147 | + Action { text: "seven" }, |
| 148 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 149 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 150 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 151 | + Action { text: "seven" }, |
| 152 | + Action { text: "seven" }, |
| 153 | + Action { text: "seven" }, |
| 154 | + Action { text: "seven" }, |
| 155 | + Action { text: "seven" }, |
| 156 | + Action { text: "seven" }, |
| 157 | + Action { text: "seven" }, |
| 158 | + Action { text: "seven" }, |
| 159 | + Action { text: "seven" }, |
| 160 | + Action { text: "seven" }, |
| 161 | + Action { text: "seven" }, |
| 162 | + Action { text: "seven" }, |
| 163 | + Action { text: "seven" }, |
| 164 | + Action { text: "seven" }, |
| 165 | + Action { text: "seven" }, |
| 166 | + Action { text: "seven" }, |
| 167 | + Action { text: "seven" }, |
| 168 | + Action { text: "seven" }, |
| 169 | + Action { text: "seven" }, |
| 170 | + Action { text: "seven" }, |
| 171 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 172 | + Action { text: "seven" }, |
| 173 | + Action { text: "seven" }, |
| 174 | + Action { text: "seven" }, |
| 175 | + Action { text: "seven" }, |
| 176 | + Action { text: "seven" }, |
| 177 | + Action { text: "seven" }, |
| 178 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 179 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 180 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 181 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 182 | + Action { text: "seven" }, |
| 183 | + Action { text: "seven" }, |
| 184 | + Action { text: "seven" }, |
| 185 | + Action { text: "seven" }, |
| 186 | + Action { text: "seven" }, |
| 187 | + Action { text: "seven" }, |
| 188 | + Action { text: "sevenoneoneoneoneoneone" }, |
| 189 | + Action { text: "seven" }, |
| 190 | + Action { text: "seven" }, |
| 191 | + Action { text: "seven" }, |
| 192 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 193 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 194 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 195 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 196 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 197 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 198 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 199 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 200 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 201 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 202 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 203 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 204 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 205 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 206 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 207 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 208 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 209 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 210 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 211 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 212 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 213 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 214 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 215 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 216 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 217 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 218 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 219 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 220 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 221 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 222 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 223 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 224 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 225 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 226 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 227 | + Action { text: "sevenoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneoneone" }, |
| 228 | + Action { text: "seven" } |
| 229 | ] |
| 230 | } |
| 231 | } |
| 232 | |
| 233 | === modified file 'modules/Ubuntu/Components/Themes/Ambiance/1.3/SectionsStyle.qml' |
| 234 | --- modules/Ubuntu/Components/Themes/Ambiance/1.3/SectionsStyle.qml 2015-06-16 23:18:45 +0000 |
| 235 | +++ modules/Ubuntu/Components/Themes/Ambiance/1.3/SectionsStyle.qml 2015-07-13 16:58:44 +0000 |
| 236 | @@ -15,20 +15,33 @@ |
| 237 | */ |
| 238 | import QtQuick 2.4 |
| 239 | import Ubuntu.Components 1.3 |
| 240 | +import QtGraphicalEffects 1.0 |
| 241 | |
| 242 | Item { |
| 243 | id: sectionsStyle |
| 244 | |
| 245 | - implicitWidth: sectionsRow.width |
| 246 | + //VISUAL CHANGES: |
| 247 | + //- sectionColor |
| 248 | + //- added underlineColor |
| 249 | + //- fontsize to medium |
| 250 | + //- font weight to Light |
| 251 | + |
| 252 | + implicitWidth: units.gu(50) //sectionsRow.width |
| 253 | implicitHeight: units.gu(4) |
| 254 | |
| 255 | enabled: styledItem.enabled |
| 256 | opacity: enabled ? 1.0 : 0.5 |
| 257 | - |
| 258 | /*! |
| 259 | The foreground color of unselected sections. |
| 260 | */ |
| 261 | - property color sectionColor: theme.palette.selected.backgroundText |
| 262 | + //FIXME: hardcoded color |
| 263 | + property color sectionColor: "#888888"//theme.palette.selected.backgroundText |
| 264 | + |
| 265 | + /*! |
| 266 | + The foreground color of underline rectangle of unselected sections. |
| 267 | + */ |
| 268 | + //FIXME: hardcoded color |
| 269 | + property color underlineColor: Qt.rgba(0,0,0,0.2)//theme.palette.selected.backgroundText |
| 270 | |
| 271 | /*! |
| 272 | The foreground color of the selected section. |
| 273 | @@ -43,7 +56,7 @@ |
| 274 | /*! |
| 275 | The font size for the text in the buttons. |
| 276 | */ |
| 277 | - property string fontSize: "small" |
| 278 | + property string fontSize: "medium" |
| 279 | |
| 280 | /*! |
| 281 | The spacing on the left and right sides of the label |
| 282 | @@ -56,30 +69,166 @@ |
| 283 | */ |
| 284 | property real underlineHeight: units.dp(2) |
| 285 | |
| 286 | - Row { |
| 287 | - id: sectionsRow |
| 288 | - anchors { |
| 289 | - top: parent.top |
| 290 | - bottom: parent.bottom |
| 291 | - horizontalCenter: parent.horizontalCenter |
| 292 | - } |
| 293 | - width: childrenRect.width |
| 294 | - |
| 295 | - Repeater { |
| 296 | - id: sectionsRepeater |
| 297 | + property real __listViewMargin: units.gu(3) |
| 298 | + property bool __hoveringLeft: false |
| 299 | + property bool __hoveringRight: false |
| 300 | + |
| 301 | + //we don't clip listview on purpose, so we have to clip here to prevent Sections element |
| 302 | + //from painting outside its area |
| 303 | + clip: true |
| 304 | + |
| 305 | + //This item is needed for the OpacityMask feature. It is needed to make sure that when we |
| 306 | + //bring a list element into view, that element won't be covered by the opacity mask. So we |
| 307 | + //disable clipping on the list but we give it margins. This way when an item is repositioned |
| 308 | + //to be within the listview, that item will not be positioned under the opacity mask. (which is |
| 309 | + //what would have happened if the listview were filling the parent) |
| 310 | + Item { |
| 311 | + id: listviewcontainer |
| 312 | + anchors.fill: parent |
| 313 | + |
| 314 | + //We need to set this to 0.0 when OpacityMask will draw this listview for us. |
| 315 | + //we don't set visible: false because we still want to get the input events! |
| 316 | + opacity: 1.0 |
| 317 | + |
| 318 | + ListView { |
| 319 | + id: sectionsListView |
| 320 | + objectName: "section_listview" |
| 321 | + |
| 322 | + property bool animateContentX: false |
| 323 | + |
| 324 | + function ensureItemIsInTheMiddle(currentItem) { |
| 325 | + if (currentItem !== null) { |
| 326 | + //stop the flick before doing computations |
| 327 | + if (moving) { |
| 328 | + return |
| 329 | + } |
| 330 | + if (dragging || flicking) { |
| 331 | + cancelFlick() |
| 332 | + } |
| 333 | + |
| 334 | + console.log("CONTENTX", contentX) |
| 335 | + if (contentXAnim.running) { contentXAnim.stop() } |
| 336 | + console.log("CONTENTXAFTER", contentX) |
| 337 | + |
| 338 | + var pos = currentItem.mapToItem(sectionsListView.contentItem, 0, 0) |
| 339 | + |
| 340 | + //In the case where we're moving to an item which is outside of the screen (this |
| 341 | + //happens when using Keyboard navigation after scrolling the view), we |
| 342 | + //try working around the fact that contentWidth/X keeps changing |
| 343 | + //by using listview's functions (WHICH DON'T HAVE ANIMATIONS though) just to position the |
| 344 | + //view more or less at the right place. After that, we'll fake an animation from one side to the middle of the |
| 345 | + //screen. |
| 346 | + /*if (pos.x + pos.width <= sectionsListView.x) { |
| 347 | + positionViewAtIndex(currentIndex, ListView.Beginning) |
| 348 | + //refresh the mapping as we've moved the view |
| 349 | + pos = currentItem.mapToItem(sectionsListView, 0, 0) |
| 350 | + |
| 351 | + } else if (pos.x >= sectionsListView.x + sectionsListView.width) { |
| 352 | + positionViewAtIndex(currentIndex, ListView.End) |
| 353 | + //refresh the mapping as we've moved the view |
| 354 | + pos = currentItem.mapToItem(sectionsListView, 0, 0) |
| 355 | + }*/ |
| 356 | + |
| 357 | + var newContentX = pos.x - sectionsListView.width/2 + currentItem.width/2 |
| 358 | + contentXAnim.from = contentX |
| 359 | + //make sure we don't overshoot bounds |
| 360 | + contentXAnim.to = Math.max(originX, Math.min(newContentX, originX + contentWidth - width)) |
| 361 | + contentXAnim.start() |
| 362 | + |
| 363 | + console.log("OUR VALUES", contentX, originX, contentWidth, contentXAnim.to) |
| 364 | + //FIXME: due to listview internal implementation and the fact that delegates don't have a fixed size, |
| 365 | + //this breaks in some cases, due to originX changing *after* we start the |
| 366 | + //animation. In those cases the current item doesn't align to the horizontal center because we compute |
| 367 | + //contentXAnim.to based on an originX which is then changed by the internals after we have already |
| 368 | + //started the animation. We could fixup the position at the end of the animation, |
| 369 | + //but that would just be a workaround. |
| 370 | + } |
| 371 | + } |
| 372 | + |
| 373 | + anchors { |
| 374 | + fill: parent |
| 375 | + leftMargin: __listViewMargin |
| 376 | + rightMargin: __listViewMargin |
| 377 | + } |
| 378 | + |
| 379 | + //this is just to disable keyboard navigation to avoid messing with contentX/contentWidth while |
| 380 | + //the view is moving |
| 381 | + focus: !moving |
| 382 | + onContentXChanged: console.log(contentX, originX, contentWidth) |
| 383 | + |
| 384 | + //FIXME: this means when we resize the window it will refocus on the current item even if it's outside of the view! |
| 385 | + //NOTE: removing this also breaks the alignment when the sections are initialized, because of contentX/contentWidth changing |
| 386 | + onWidthChanged: ensureItemIsInTheMiddle(currentItem) |
| 387 | + |
| 388 | + orientation: ListView.Horizontal |
| 389 | + |
| 390 | + //We want to block dragging but at the same time we want the keyboard to work!!! |
| 391 | + //interactive: contentWidth > width |
| 392 | + boundsBehavior: Flickable.StopAtBounds |
| 393 | + |
| 394 | model: styledItem.model |
| 395 | - objectName: "sections_repeater" |
| 396 | - AbstractButton { |
| 397 | + |
| 398 | + //We need this to make sure that we have delegates for the whole width, since we have |
| 399 | + //clip disabled. Read more |
| 400 | + displayMarginBeginning: __listViewMargin |
| 401 | + displayMarginEnd: __listViewMargin |
| 402 | + |
| 403 | + //make sure that the currentItem is in the middle when everything is initialized |
| 404 | + Component.onCompleted: ensureItemIsInTheMiddle(currentItem) |
| 405 | + |
| 406 | + //FIXME: keyboard navigation offered by ListView will break this, won't it? |
| 407 | + currentIndex: styledItem.selectedIndex |
| 408 | + onCurrentIndexChanged: { |
| 409 | + styledItem.selectedIndex = currentIndex |
| 410 | + } |
| 411 | + onCurrentItemChanged: { |
| 412 | + //adjust contentX so that the item is kept in the middle |
| 413 | + //don't use ListView.ApplyRange because that does an awkward animation when you select an item |
| 414 | + //*while* the current item is outside of screen |
| 415 | + ensureItemIsInTheMiddle(currentItem) |
| 416 | + } |
| 417 | + |
| 418 | + //highlightMoveDuration: UbuntuAnimation.BriskDuration |
| 419 | + //highlightResizeDuration: UbuntuAnimation.BriskDuration |
| 420 | + |
| 421 | + highlightFollowsCurrentItem: false |
| 422 | + //DON'T use ApplyRange, because it will cause a weird animation when you select an item which is on screen |
| 423 | + //with the previously selected item being out of screen |
| 424 | + //highlightRangeMode: ListView.ApplyRange |
| 425 | + //preferredHighlightEnd: width/2 + currentItem.width/2 |
| 426 | + //preferredHighlightBegin: width/2 - currentItem.width/2 |
| 427 | + |
| 428 | + //highlightFollowsCurrentItem will resize the highlight element to fill the delegate, |
| 429 | + //so we need an Item in the middle to set a height for the highlight rectangle different |
| 430 | + //from the delegate size |
| 431 | + highlight: Item { |
| 432 | + //show the highlight on top of the delegate |
| 433 | + z: 2 |
| 434 | + |
| 435 | + x: sectionsListView.currentItem ? sectionsListView.currentItem.x : -width |
| 436 | + width: sectionsListView.currentItem ? sectionsListView.currentItem.width : 0 |
| 437 | + height: sectionsListView.currentItem ? sectionsListView.currentItem.height : 0 |
| 438 | + |
| 439 | + Rectangle { |
| 440 | + anchors { |
| 441 | + bottom: parent.bottom |
| 442 | + left: parent.left |
| 443 | + right: parent.right |
| 444 | + } |
| 445 | + height: sectionsStyle.underlineHeight |
| 446 | + color: sectionsStyle.selectedSectionColor |
| 447 | + } |
| 448 | + |
| 449 | + Behavior on x { UbuntuNumberAnimation {} } |
| 450 | + } |
| 451 | + |
| 452 | + delegate: AbstractButton { |
| 453 | id: sectionButton |
| 454 | - anchors { |
| 455 | - top: parent ? parent.top : undefined |
| 456 | - bottom: parent ? parent.bottom : undefined |
| 457 | - } |
| 458 | objectName: "section_button_" + index |
| 459 | width: label.width + 2 * sectionsStyle.horizontalLabelSpacing |
| 460 | - height: sectionsRow.height |
| 461 | + height: sectionsStyle.height |
| 462 | property bool selected: index === styledItem.selectedIndex |
| 463 | - onClicked: styledItem.selectedIndex = index |
| 464 | + onClicked: { styledItem.selectedIndex = index; sectionsListView.forceActiveFocus() } |
| 465 | |
| 466 | // Background pressed highlight |
| 467 | Rectangle { |
| 468 | @@ -95,10 +244,14 @@ |
| 469 | // modelData may be either a string, or an Action |
| 470 | text: modelData.hasOwnProperty("text") ? modelData.text : modelData |
| 471 | fontSize: sectionsStyle.fontSize |
| 472 | + font.weight: Font.Light |
| 473 | anchors.centerIn: parent |
| 474 | color: sectionButton.selected ? |
| 475 | sectionsStyle.selectedSectionColor : |
| 476 | sectionsStyle.sectionColor |
| 477 | + |
| 478 | + //FIXME: color animation? is that ok to design? what's the duration? |
| 479 | + Behavior on color { ColorAnimation { duration: 500 } } |
| 480 | } |
| 481 | |
| 482 | // Section title underline |
| 483 | @@ -109,11 +262,151 @@ |
| 484 | right: parent.right |
| 485 | } |
| 486 | height: sectionsStyle.underlineHeight |
| 487 | - color: sectionButton.selected ? |
| 488 | - sectionsStyle.selectedSectionColor : |
| 489 | - sectionsStyle.sectionColor |
| 490 | + color: sectionsStyle.underlineColor |
| 491 | } |
| 492 | } |
| 493 | - } |
| 494 | - } |
| 495 | + |
| 496 | + SmoothedAnimation { |
| 497 | + id: contentXAnim |
| 498 | + target: sectionsListView |
| 499 | + property: "contentX" |
| 500 | + duration: UbuntuAnimation.FastDuration |
| 501 | + velocity: units.gu(10) |
| 502 | + //alwaysRunToEnd: true |
| 503 | + } |
| 504 | + |
| 505 | + //Behavior on contentX { SmoothedAnimation { duration: UbuntuAnimation.FastDuration; velocity: units.gu(10)} } |
| 506 | + |
| 507 | + } |
| 508 | + } |
| 509 | + |
| 510 | + MouseArea { |
| 511 | + id: hoveringArea |
| 512 | + |
| 513 | + property real iconsDisabledOpacity: 0.3 |
| 514 | + |
| 515 | + function checkHovering(mouse) { |
| 516 | + if (mouse.x < __listViewMargin) { |
| 517 | + if (!__hoveringLeft) __hoveringLeft = true |
| 518 | + } else if (mouse.x > width - __listViewMargin ) { |
| 519 | + if (!__hoveringRight) __hoveringRight = true |
| 520 | + } else { |
| 521 | + __hoveringLeft = false |
| 522 | + __hoveringRight = false |
| 523 | + } |
| 524 | + } |
| 525 | + |
| 526 | + onContainsMouseChanged: console.log(containsMouse) |
| 527 | + anchors.fill: parent |
| 528 | + hoverEnabled: true |
| 529 | + |
| 530 | + onPositionChanged: checkHovering(mouse) |
| 531 | + onExited: { |
| 532 | + __hoveringLeft = false |
| 533 | + __hoveringRight = false |
| 534 | + } |
| 535 | + onPressed: if (!__hoveringLeft && !__hoveringRight) { |
| 536 | + mouse.accepted = false |
| 537 | + } |
| 538 | + onClicked: { |
| 539 | + //scroll the list to bring the element which is under the cursor into the view |
| 540 | + //var item = sectionsListView.itemAt(mouse.x + sectionsListView.contentX - __listViewMargin, mouse.y) |
| 541 | + //if (item !== null) { |
| 542 | + //We could use positionViewAtIndex(...) here but it wouldn't provide animation |
| 543 | + |
| 544 | + if (contentXAnim.running) contentXAnim.stop() |
| 545 | + |
| 546 | + //Scroll one item at a time with animation |
| 547 | + //sectionsListView.contentX = __hoveringLeft ? item.mapToItem(sectionsListView.contentItem, 0,0).x : item.mapToItem(sectionsListView.contentItem, 0,0).x - sectionsListView.width + item.width |
| 548 | + var newContentX = sectionsListView.contentX + (sectionsListView.width * (__hoveringLeft ? -1 : 1)) |
| 549 | + |
| 550 | + contentXAnim.from = sectionsListView.contentX |
| 551 | + //make sure we don't overshoot bounds |
| 552 | + contentXAnim.to = Math.max(sectionsListView.originX, Math.min(newContentX, sectionsListView.originX + sectionsListView.contentWidth - sectionsListView.width)) |
| 553 | + |
| 554 | + contentXAnim.start() |
| 555 | + |
| 556 | + //} |
| 557 | + } |
| 558 | + |
| 559 | + Icon { |
| 560 | + id: leftHoveringIcon |
| 561 | + anchors.left: parent.left |
| 562 | + anchors.leftMargin: units.gu(1) |
| 563 | + anchors.verticalCenter: parent.verticalCenter |
| 564 | + width: units.gu(1) |
| 565 | + height: units.gu(1) |
| 566 | + visible: false |
| 567 | + rotation: 180 |
| 568 | + opacity: sectionsListView.atXBeginning ? hoveringArea.iconsDisabledOpacity : 1.0 |
| 569 | + name: "chevron" |
| 570 | + } |
| 571 | + |
| 572 | + Icon { |
| 573 | + id: rightHoveringIcon |
| 574 | + anchors.right: parent.right |
| 575 | + anchors.rightMargin: units.gu(1) |
| 576 | + anchors.verticalCenter: parent.verticalCenter |
| 577 | + width: units.gu(1) |
| 578 | + height: units.gu(1) |
| 579 | + visible: false |
| 580 | + opacity: sectionsListView.atXEnd ? hoveringArea.iconsDisabledOpacity : 1.0 |
| 581 | + name: "chevron" |
| 582 | + } |
| 583 | + } |
| 584 | + |
| 585 | + Rectangle { |
| 586 | + anchors.left: parent.left |
| 587 | + anchors.right: parent.right |
| 588 | + anchors.bottom: parent.bottom |
| 589 | + height: units.dp(1) |
| 590 | + color: sectionsStyle.underlineColor |
| 591 | + } |
| 592 | + |
| 593 | + LinearGradient { |
| 594 | + id: gradient |
| 595 | + |
| 596 | + visible: false |
| 597 | + anchors.fill: parent |
| 598 | + start: Qt.point(0,0) |
| 599 | + end: Qt.point(width,0) |
| 600 | + |
| 601 | + property real __gradientWidth: units.gu(3) / gradient.width |
| 602 | + //the width is __gradientWidth, but we want the gradient to actually start/finish at __gradientSplitPosition |
| 603 | + //just to leave some margin. |
| 604 | + property real __gradientSplitPosition: 3/4 * __gradientWidth |
| 605 | + |
| 606 | + gradient: Gradient { |
| 607 | + //left gradient |
| 608 | + GradientStop { position: 0.0 ; color: Qt.rgba(1,1,1,0) } |
| 609 | + GradientStop { position: gradient.__gradientSplitPosition ; color: Qt.rgba(1,1,1,0) } |
| 610 | + GradientStop { position: gradient.__gradientWidth; color: Qt.rgba(1,1,1,1) } |
| 611 | + //right gradient |
| 612 | + GradientStop { position: 1.0 - gradient.__gradientWidth; color: Qt.rgba(1,1,1,1) } |
| 613 | + GradientStop { position: 1.0 - gradient.__gradientSplitPosition; color: Qt.rgba(1,1,1,0) } |
| 614 | + GradientStop { position: 1.0; color: Qt.rgba(1,1,1,0) } |
| 615 | + } |
| 616 | + |
| 617 | + } |
| 618 | + |
| 619 | + OpacityMask { |
| 620 | + id: mask |
| 621 | + anchors.fill: parent |
| 622 | + |
| 623 | + visible: false |
| 624 | + source: listviewcontainer |
| 625 | + maskSource: gradient |
| 626 | + } |
| 627 | + |
| 628 | + //Since we only show one arrow at a time, let's reuse the same item and handle the property changes with states |
| 629 | + states: [ |
| 630 | + State { |
| 631 | + name: "hovering" |
| 632 | + when: hoveringArea.containsMouse |
| 633 | + PropertyChanges { target: mask; visible: true } |
| 634 | + PropertyChanges { target: listviewcontainer; opacity: 0.0 } |
| 635 | + PropertyChanges { target: leftHoveringIcon; visible: true; } |
| 636 | + PropertyChanges { target: rightHoveringIcon; visible: true; } |
| 637 | + } |
| 638 | + ] |
| 639 | } |

FAILED: Continuous integration, rev:1541 jenkins. qa.ubuntu. com/job/ ubuntu- sdk-team- ubuntu- ui-toolkit- staging- ci/1991/ jenkins. qa.ubuntu. com/job/ ubuntu- sdk-team- ubuntu- ui-toolkit- staging- vivid-amd64- ci/719 jenkins. qa.ubuntu. com/job/ ubuntu- sdk-team- ubuntu- ui-toolkit- staging- vivid-armhf- ci/721 jenkins. qa.ubuntu. com/job/ ubuntu- sdk-team- ubuntu- ui-toolkit- staging- vivid-armhf- ci/721/ artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ ubuntu- sdk-team- ubuntu- ui-toolkit- staging- vivid-i386- ci/718
http://
Executed test runs:
UNSTABLE: http://
SUCCESS: http://
deb: http://
UNSTABLE: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/ubuntu- sdk-team- ubuntu- ui-toolkit- staging- ci/1991/ rebuild
http://