Merge lp:~faenil/ubuntu-ui-toolkit/WIP_BROKEN_scrollableSectionsStyle into lp:ubuntu-ui-toolkit/staging

Proposed by Andrea Bernabei on 2015-07-13
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
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: mp+264616@code.launchpad.net

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.

---------------IMPLEMENTATION NOTE-------------
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/X/originX will not change as the view scrolls as it happens with ListView, which trades mental sanity for memory consumption (and that is understandable!).

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.

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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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 }

Subscribers

People subscribed via source and target branches