on xenial help sign in
Add TabBar to the Dash header navigation

Changes this comes with: * DashBar at the bottom is gone * PageHeader doesn't have a Label anymore, it has the childItem property where you add which thing it has to contain * New: PageHeaderLabel mimics the old behaviour of PageHeader * The header of the LVWPH of GenericScopeView is now fake and only used for positioning. There is a single global floating header in DashContent (which is a PageHeader with a TabBar as childItem) * The GenericScopeView previewListView and OpenEffect have been also moved to the DashContent so that the openEffect includes the floating header in the "animation"

commit7b1a85f3cb1685442b55894b32621b2b02c1768a authorAlbert Astals <albert.astals@canonical.com> dateTue 5 Nov 2013, 17:06 (13 years ago) parenta7cab4c
18 files changed+564−606
Components/PageHeader.qml+22−37 Components/PageHeaderLabel.qml+41 Dash/Dash.qml−18 Dash/DashBar.qml−128 Dash/DashContent.qml+142−58 Dash/DashContentOpenEffect.qml+57 Dash/GenericScopeView.qml+17−215 Dash/PreviewListView.qml+201 Dash/ScopeView.qml+7−1 Hud/HudParametrizedActionsPage.qml+1−1 Shell.qml−2 tests/qmltests/CMakeLists.txt+1−2 tests/qmltests/Components/tst_PageHeader.qml → tests/qmltests/Components/tst_PageHeaderLabel.qml+2−2 tests/qmltests/Dash/qml/FakeScopeView.qml+1 tests/qmltests/Dash/tst_Dash.qml+6−8 tests/qmltests/Dash/tst_DashBar.qml−111 tests/qmltests/Dash/tst_DashContent.qml+23−3 tests/qmltests/Dash/tst_GenericScopeView.qml+43−20
Components/PageHeader.qml+22−37
@@ -21,17 +21,12 @@ import Ubuntu.Components.ListItems 0.1 as ListItem
2121import Unity 0.1
2222
2323Item {
24− /*!
25− \preliminary
26− The text that is shown inside the Page Header
27− \qmlproperty string text
28− */
29− property alias text: label.text
30−
24+ id: root
3125 property bool searchEntryEnabled: false
3226 property alias searchQuery: searchField.text
3327 property ListModel searchHistory: SearchHistoryModel {}
3428 property Scope scope
29+ property alias childItem: itemContainer.children
3530
3631 height: units.gu(8.5)
3732 implicitHeight: units.gu(8.5)
@@ -82,29 +77,10 @@ Item {
8277 height: childrenRect.height
8378
8479 Item {
85− id: textContainer
80+ id: itemContainer
8681
87− width: header.width
82+ width: searchContainer.narrowMode ? header.width : header.width - searchContainer.width
8883 height: header.height
89−
90− Label {
91− id: label
92− anchors {
93− left: parent.left
94− leftMargin: units.gu(2)
95− right: parent.right
96− verticalCenter: parent.verticalCenter
97− }
98−
99− color: Theme.palette.selected.backgroundText
100− opacity: 0.8
101− font.family: "Ubuntu"
102− font.weight: Font.Light
103− fontSize: "x-large"
104− elide: Text.ElideRight
105− style: Text.Raised
106− styleColor: "black"
107− }
10884 }
10985
11086 Item {
@@ -113,12 +89,21 @@ Item {
11389
11490 visible: searchEntryEnabled
11591
116− property bool narrowMode: parent.width < label.contentWidth + units.gu(50)
92+ property bool narrowMode: {
93+ if (root.childItem.length == 0)
94+ return false;
95+ if (root.childItem.length > 1) {
96+ console.log("ERROR: PageHeader childItem must be a single item");
97+ return false;
98+ }
99+ var item = root.childItem[0];
100+ return parent.width < item.implicitWidth + units.gu(50)
101+ }
117102
118103 property bool active: searchField.text != "" || searchField.activeFocus
119104 property var popover: null
120105
121− anchors.right: textContainer.right
106+ anchors.right: headerContainer.right
122107 height: header.height
123108
124109 state:
@@ -237,13 +222,13 @@ Item {
237222 states: [
238223 State {
239224 name: "wide"
240− AnchorChanges { target: textContainer; anchors.top: headerContainer.top }
241− AnchorChanges { target: searchContainer; anchors.left: undefined; anchors.top: textContainer.top }
225+ AnchorChanges { target: itemContainer; anchors.top: headerContainer.top }
226+ AnchorChanges { target: searchContainer; anchors.left: undefined; anchors.top: itemContainer.top }
242227 },
243228 State {
244229 name: "narrow"
245230 PropertyChanges { target: searchField; highlighted: true }
246− AnchorChanges { target: textContainer; anchors.top: searchContainer.bottom }
231+ AnchorChanges { target: itemContainer; anchors.top: searchContainer.bottom }
247232 AnchorChanges { target: searchContainer; anchors.left: headerContainer.left; anchors.top: headerContainer.top }
248233 },
249234 State {
@@ -281,7 +266,7 @@ Item {
281266 ParallelAnimation {
282267 NumberAnimation { targets: [searchContainer, searchField]; property: "width"; duration: 200; easing.type: Easing.InOutQuad }
283268 PropertyAction { target: primaryImage; property: "source" }
284− AnchorAnimation { targets: [searchContainer, textContainer]; duration: 200; easing.type: Easing.InOutQuad }
269+ AnchorAnimation { targets: [searchContainer, itemContainer]; duration: 200; easing.type: Easing.InOutQuad }
285270 }
286271 ScriptAction { script: searchContainer.openPopover() }
287272 }
@@ -290,14 +275,14 @@ Item {
290275 to: "inactive"
291276 ScriptAction { script: searchContainer.closePopover() }
292277 NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.InOutQuad }
293− AnchorAnimation { targets: [searchContainer, textContainer]; duration: 200; easing.type: Easing.InOutQuad }
278+ AnchorAnimation { targets: [searchContainer, itemContainer]; duration: 200; easing.type: Easing.InOutQuad }
294279 },
295280 Transition {
296281 to: "narrowActive"
297282 SequentialAnimation {
298283 ParallelAnimation {
299284 NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.OutQuad }
300− AnchorAnimation { targets: [searchContainer, textContainer]; duration: 200; easing.type: Easing.InOutQuad }
285+ AnchorAnimation { targets: [searchContainer, itemContainer]; duration: 200; easing.type: Easing.InOutQuad }
301286 }
302287 ScriptAction { script: searchContainer.openPopover() }
303288 }
@@ -306,7 +291,7 @@ Item {
306291 to: "narrowInactive"
307292 ScriptAction { script: searchContainer.closePopover() }
308293 NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.OutQuad }
309− AnchorAnimation { targets: [searchContainer, textContainer]; duration: 200; easing.type: Easing.InOutQuad }
294+ AnchorAnimation { targets: [searchContainer, itemContainer]; duration: 200; easing.type: Easing.InOutQuad }
310295 }
311296 ]
312297
Components/PageHeaderLabel.qmlnew file+41
@@ -0,0 +1,41 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import Ubuntu.Components 0.1
19+
20+PageHeader {
21+ property alias text: label.text
22+
23+ childItem: Label {
24+ id: label
25+ anchors {
26+ left: parent.left
27+ leftMargin: units.gu(2)
28+ right: parent.right
29+ verticalCenter: parent.verticalCenter
30+ }
31+
32+ color: Theme.palette.selected.backgroundText
33+ opacity: 0.8
34+ font.family: "Ubuntu"
35+ font.weight: Font.Light
36+ fontSize: "x-large"
37+ elide: Text.ElideRight
38+ style: Text.Raised
39+ styleColor: "black"
40+ }
41+}
Dash/Dash.qml−18
@@ -63,11 +63,6 @@ Showable {
6363 anchors.fill: parent
6464 model: filteredScopes
6565 scopes: scopes
66− onMovementStarted: dashbar.startNavigation()
67− onMovementEnded: dashbar.stopNavigation()
68− onContentFlickStarted: dashbar.finishNavigation()
69− onContentEndReached: dashbar.finishNavigation()
70− onPreviewShown: dashbar.finishNavigation()
7166 onScopeLoaded: {
7267 if (scopeId == dash.showScopeOnLoaded) {
7368 dash.setCurrentScope(scopeId, false, false)
@@ -77,17 +72,4 @@ Showable {
7772 scale: dash.contentScale
7873 clip: scale != 1.0
7974 }
80−
81− DashBar {
82− id: dashbar
83− objectName: "dashbar"
84− anchors.bottom: parent.bottom
85− anchors.left: parent.left
86− anchors.right: parent.right
87− model: filteredScopes
88− currentIndex: dashContent.currentIndex
89− onItemSelected: dashContent.setCurrentScopeAtIndex(index, true, false)
90− opacity: dash.contentScale == 1.0 ? 1.0 : 0.0
91− Behavior on opacity { NumberAnimation { easing.type: Easing.OutQuad; duration: 150 } }
92− }
9375}
Dash/DashBar.qmldeleted−128
@@ -1,128 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import Ubuntu.Components 0.1
19−import Unity 0.1
20−
21−Item {
22− id: dashBar
23−
24− property var model
25− property alias currentIndex: row.currentIndex
26−
27− property int lineHeight: units.dp(2)
28− property int itemSize: units.gu(7)
29− property int iconSize: units.gu(3.5)
30−
31− signal itemSelected(int index)
32−
33− width: units.gu(40)
34− height: units.gu(6)
35−
36− function startNavigation() {
37− timeout.stop()
38− panel.open()
39− }
40−
41− function stopNavigation() {
42− timeout.restart()
43− }
44−
45− function finishNavigation() {
46− panel.close()
47− }
48−
49− Timer {
50− id: timeout
51− interval: 1500
52− running: false
53− repeat: false
54− onTriggered: finishNavigation()
55− }
56−
57− Panel {
58− id: panel
59− objectName: "panel"
60− anchors.fill: parent
61−
62− locked: true // TODO: remove this when lp bug #1179569 will be fixed
63−
64− Rectangle {
65− color: "black"
66− anchors.fill: parent
67−
68− ListView {
69− id: row
70− objectName: "row"
71− model: dashBar.model
72− orientation: ListView.Horizontal
73− width: Math.min(Math.max(dashBar.width/2, units.gu(40)), count * itemSize)
74− height: parent.height
75− anchors.horizontalCenter: parent.horizontalCenter
76− onMovingChanged: if (moving) { timeout.stop() } else { timeout.restart() }
77− interactive: visibleArea.widthRatio < 1 && panel.opened
78− highlightFollowsCurrentItem: false
79−
80− onCurrentItemChanged: {
81− highlightLine.width = currentItem.width
82− highlightLine.x = x + currentItem.x
83− }
84−
85− delegate:
86− // FIXME: Use Panel's integrated trigger() once that works again.
87− MouseArea {
88− width: itemSize
89− height: dashBar.height
90− anchors.top: parent.top
91−
92− onClicked: {
93− dashBar.itemSelected(index)
94− timeout.restart()
95− }
96−
97− Image {
98− anchors.centerIn: parent
99− source: scope.iconHint
100− sourceSize { width: iconSize; height: iconSize }
101− // opacity: index == currentIndex ? 1 : 1 // same opacity for now
102− }
103− }
104− }
105− }
106− }
107−
108− Rectangle {
109− anchors {
110− left: parent.left
111− right: parent.right
112− bottom: parent.bottom
113− }
114− height: dashBar.lineHeight
115− color: "black"
116−
117− Rectangle {
118− id: highlightLine
119− color: Theme.palette.selected.foreground
120− height: parent.height
121− anchors.bottom: parent.bottom
122− z: 1
123−
124− Behavior on x {NumberAnimation { duration: 150; easing.type: Easing.OutCubic}}
125− Behavior on width {NumberAnimation { duration: 150; easing.type: Easing.OutCubic}}
126− }
127− }
128−}
Dash/DashContent.qml+142−58
@@ -17,6 +17,7 @@
1717import QtQuick 2.0
1818import Ubuntu.Components 0.1
1919import Unity 0.1
20+import "../Components"
2021
2122Item {
2223 id: dashContent
@@ -32,7 +33,6 @@ Item {
3233 signal movementEnded()
3334 signal contentFlickStarted()
3435 signal contentEndReached()
35− signal previewShown()
3636 signal scopeLoaded(string scopeId)
3737 signal positionedAtBeginning()
3838
@@ -79,71 +79,155 @@ Item {
7979 }
8080 }
8181
82− ListView {
83− id: dashContentList
84− objectName: "dashContentList"
82+ Item {
83+ id: dashContentListHolder
84+ anchors.fill: parent
8585
86− interactive: dashContent.scopes.loaded && !currentItem.previewShown && !currentItem.moving
86+ ListView {
87+ id: dashContentList
88+ objectName: "dashContentList"
89+
90+ interactive: dashContent.scopes.loaded && !previewListView.onScreen && !currentItem.moving
91+
92+ anchors.fill: parent
93+ model: dashContent.model
94+ orientation: ListView.Horizontal
95+ boundsBehavior: Flickable.DragAndOvershootBounds
96+ flickDeceleration: units.gu(625)
97+ maximumFlickVelocity: width * 5
98+ snapMode: ListView.SnapOneItem
99+ highlightMoveDuration: 250
100+ highlightRangeMode: ListView.StrictlyEnforceRange
101+ // TODO Investigate if we can switch to a smaller cache buffer when/if UbuntuShape gets more performant
102+ cacheBuffer: 1073741823
103+ onMovementStarted: dashContent.movementStarted()
104+ onMovementEnded: dashContent.movementEnded()
105+
106+ // If the number of items is less than the current index, then need to reset to another item.
107+ onCountChanged: {
108+ if (currentIndex >= count)
109+ dashContent.setCurrentScopeAtIndex(count-1, true, true)
110+ }
87111
88− anchors.fill: parent
89− model: dashContent.model
90− orientation: ListView.Horizontal
91− boundsBehavior: Flickable.DragAndOvershootBounds
92− flickDeceleration: units.gu(625)
93− maximumFlickVelocity: width * 5
94− snapMode: ListView.SnapOneItem
95− highlightMoveDuration: 250
96− highlightRangeMode: ListView.StrictlyEnforceRange
97− /* FIXME: workaround rendering issue due to use of ShaderEffectSource in
98− UbuntuShape. While switching from the home scope to the People scope the
99− rendering would block midway.
100− */
101− cacheBuffer: 2147483647
102− onMovementStarted: dashContent.movementStarted()
103− onMovementEnded: dashContent.movementEnded()
104−
105− // If the number of items is less than the current index, then need to reset to another item.
106− onCountChanged: {
107− if (currentIndex >= count)
108− dashContent.setCurrentScopeAtIndex(count-1, true, true)
112+ delegate:
113+ Loader {
114+ width: ListView.view.width
115+ height: ListView.view.height
116+ asynchronous: true
117+ source: scopeMapper.map(scope.id)
118+ objectName: scope.id + " loader"
119+
120+ readonly property bool moving: item ? item.moving : false
121+ readonly property var categoryView: item ? item.categoryView : null
122+ readonly property Scope theScope: scope
123+
124+ // these are needed for autopilot tests
125+ readonly property string scopeId: scope.id
126+ readonly property bool isCurrent: ListView.isCurrentItem
127+ readonly property bool isLoaded: status == Loader.Ready
128+
129+ onLoaded: {
130+ item.scope = Qt.binding(function() { return scope })
131+ item.isCurrent = Qt.binding(function() { return visible && ListView.isCurrentItem })
132+ item.tabBarHeight = pageHeader.implicitHeight;
133+ item.pageHeader = pageHeader;
134+ item.openEffect = openEffect;
135+ item.previewListView = previewListView;
136+ dashContentList.movementStarted.connect(item.movementStarted)
137+ dashContent.positionedAtBeginning.connect(item.positionedAtBeginning)
138+ dashContent.scopeLoaded(item.scope.id)
139+ }
140+ Connections {
141+ target: item
142+ ignoreUnknownSignals: true
143+ onEndReached: contentEndReached()
144+ onHeaderPositionChanged: {
145+ pageHeader.y = position;
146+ }
147+ onHeaderHeightChanged: {
148+ pageHeader.height = height;
149+ }
150+ }
151+
152+ Component.onDestruction: active = false
153+ }
109154 }
110155
111− delegate:
112− Loader {
113− width: ListView.view.width
114− height: ListView.view.height
115− asynchronous: true
116− source: scopeMapper.map(scope.id)
117− objectName: scope.id + " loader"
118−
119− readonly property bool previewShown: item ? item.previewShown : false
120− readonly property bool moving: item ? item.moving : false
121−
122− // these are needed for autopilot tests
123− readonly property string scopeId: scope.id
124− readonly property bool isCurrent: ListView.isCurrentItem
125− readonly property bool isLoaded: status == Loader.Ready
126−
127− onLoaded: {
128− item.scope = Qt.binding(function() { return scope })
129− item.isCurrent = Qt.binding(function() { return visible && ListView.isCurrentItem })
130− item.searchHistory = Qt.binding(function() { return shell.searchHistory })
131− dashContentList.movementStarted.connect(item.movementStarted)
132− dashContent.positionedAtBeginning.connect(item.positionedAtBeginning)
133− dashContent.scopeLoaded(item.scope.id)
156+ PageHeader {
157+ id: pageHeader
158+ width: parent.width
159+ searchEntryEnabled: true
160+
161+ childItem: TabBar {
162+ id: tabbar
163+ objectName: "tabbar"
164+ height: units.gu(7)
165+ width: parent.width
166+ implicitWidth: currentButtonWidth
167+
168+ property alias selectedTabIndex: tabs.selectedTabIndex
169+
170+ Component.onCompleted: {
171+ __styleInstance.headerTextStyle = Text.Raised
172+ __styleInstance.headerTextStyleColor = "black"
134173 }
135− Connections {
136− target: item
137− ignoreUnknownSignals: true
138− onEndReached: contentEndReached()
139− onPreviewShownChanged: {
140− if (item.previewShown) {
141− dashContent.previewShown()
174+
175+ tabsItem: TabsBase {
176+ id: tabs
177+
178+ Repeater {
179+ model: dashContentList.model
180+ Tab {
181+ id: tab
182+ title: scope.name
183+ Component.onCompleted: {
184+ var aux = tabs.tabList;
185+ aux.push(tab)
186+ tabs.tabList = aux;
187+ tabs.modelChanged();
188+ if (tabs.selectedTabIndex == -1) {
189+ tabs.selectedTabIndex = 0;
190+ }
191+ }
192+ Component.onDestruction: {
193+ var aux = tabs.tabList;
194+ aux.splice(aux.indexOf(tab), 1)
195+ tabs.tabList = aux;
196+ tabs.modelChanged();
197+ }
142198 }
143199 }
144− }
145200
146− Component.onDestruction: active = false
201+ onSelectedTabIndexChanged: {
202+ dashContentList.currentIndex = selectedTabIndex;
203+ }
204+
205+ Connections {
206+ target: dashContentList
207+ onCurrentIndexChanged: {
208+ tabs.selectedTabIndex = dashContentList.currentIndex
209+ }
210+ }
211+ }
147212 }
213+ }
214+ }
215+
216+ DashContentOpenEffect {
217+ id: openEffect
218+ anchors {
219+ fill: parent
220+ bottomMargin: -bottomOverflow
221+ }
222+ sourceItem: dashContentListHolder
223+ previewListView: previewListView
224+ }
225+
226+ PreviewListView {
227+ id: previewListView
228+ openEffect: openEffect
229+ categoryView: dashContentList.currentItem ? dashContentList.currentItem.categoryView : null
230+ scope: dashContentList.currentItem ? dashContentList.currentItem.theScope : null
231+ anchors.fill: parent
148232 }
149233}
Dash/DashContentOpenEffect.qmlnew file+57
@@ -0,0 +1,57 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import Ubuntu.Components 0.1
19+import "../Components"
20+
21+OpenEffect {
22+ property PreviewListView previewListView: null
23+
24+ objectName: "openEffect"
25+ anchors {
26+ fill: parent
27+ bottomMargin: -bottomOverflow
28+ }
29+
30+ enabled: gap > 0.0
31+
32+ topGapPx: (1 - gap) * positionPx
33+ topOpacity: (1 - gap * 1.2)
34+ bottomGapPx: positionPx + gap * (targetBottomGapPx - positionPx)
35+ bottomOverflow: units.gu(20)
36+ live: !expansionAnimation.running
37+
38+ property int targetBottomGapPx: height - units.gu(8) - bottomOverflow
39+ property real gap: previewListView.open ? 1.0 : 0.0
40+
41+ Behavior on gap {
42+ NumberAnimation {
43+ id: expansionAnimation
44+ duration: 200
45+ easing.type: Easing.InOutQuad
46+ onRunningChanged: {
47+ if (!previewListView.open && !running) {
48+ previewListView.onScreen = false
49+ }
50+ }
51+ }
52+ }
53+ Behavior on positionPx {
54+ enabled: previewListView.open
55+ UbuntuNumberAnimation {}
56+ }
57+}
Dash/GenericScopeView.qml+17−215
@@ -21,8 +21,8 @@ import "../Components/ListItems" as ListItems
2121
2222ScopeView {
2323 id: scopeView
24− readonly property alias previewShown: previewListView.onScreen
2524 property bool enableHeightBehaviorOnNextCreation: false
25+ property var categoryView: categoryView
2626
2727 moving: categoryView.moving
2828
@@ -39,12 +39,14 @@ ScopeView {
3939 target: scopeView.scope
4040 property: "searchQuery"
4141 value: pageHeader.searchQuery
42+ when: isCurrent
4243 }
4344
4445 Binding {
4546 target: pageHeader
4647 property: "searchQuery"
4748 value: scopeView.scope.searchQuery
49+ when: isCurrent
4850 }
4951
5052 Connections {
@@ -74,6 +76,9 @@ ScopeView {
7476 property string expandedCategoryId: ""
7577 signal correctExpandedCategory();
7678
79+ onContentYChanged: pageHeader.positionRealHeader();
80+ onOriginYChanged: pageHeader.positionRealHeader();
81+
7782 Behavior on contentY {
7883 enabled: previewListView.open
7984 UbuntuNumberAnimation {}
@@ -130,7 +135,7 @@ ScopeView {
130135 target: rendererLoader.item
131136 onClicked: {
132137 // Prepare the preview in case activate() triggers a preview only
133− effect.positionPx = mapToItem(categoryView, 0, itemY).y
138+ openEffect.positionPx = mapToItem(categoryView, 0, itemY).y
134139 previewListView.categoryId = categoryId
135140 previewListView.categoryDelegate = rendererLoader.item
136141 previewListView.model = model;
@@ -151,7 +156,7 @@ ScopeView {
151156 }
152157 }
153158 onPressAndHold: {
154− effect.positionPx = mapToItem(categoryView, 0, itemY).y
159+ openEffect.positionPx = mapToItem(categoryView, 0, itemY).y
155160 previewListView.categoryId = categoryId
156161 previewListView.categoryDelegate = rendererLoader.item
157162 previewListView.model = model;
@@ -241,13 +246,14 @@ ScopeView {
241246 categoryView.expandedCategoryId = "";
242247 }
243248 }
244− pageHeader: PageHeader {
245− id: pageHeader
246− objectName: "pageHeader"
247− width: categoryView.width
248− text: scopeView.scope.name
249− searchEntryEnabled: true
250− scope: scopeView.scope
249+ pageHeader: Item {
250+ implicitHeight: scopeView.tabBarHeight
251+ onHeightChanged: scopeView.headerHeightChanged(height)
252+ onYChanged: positionRealHeader();
253+
254+ function positionRealHeader() {
255+ scopeView.headerPositionChanged(y + parent.y)
256+ }
251257 }
252258 }
253259
@@ -292,44 +298,6 @@ ScopeView {
292298 }
293299 }
294300
295− OpenEffect {
296− id: effect
297− objectName: "openEffect"
298− anchors {
299− fill: parent
300− bottomMargin: -bottomOverflow
301− }
302− sourceItem: categoryView
303−
304− enabled: gap > 0.0
305−
306− topGapPx: (1 - gap) * positionPx
307− topOpacity: (1 - gap * 1.2)
308− bottomGapPx: positionPx + gap * (targetBottomGapPx - positionPx)
309− bottomOverflow: units.gu(20)
310− live: !expansionAnimation.running
311−
312− property int targetBottomGapPx: height - units.gu(8) - bottomOverflow
313− property real gap: previewListView.open ? 1.0 : 0.0
314−
315− Behavior on gap {
316− NumberAnimation {
317− id: expansionAnimation
318− duration: 200
319− easing.type: Easing.InOutQuad
320− onRunningChanged: {
321− if (!previewListView.open && !running) {
322− previewListView.onScreen = false
323− }
324− }
325− }
326− }
327− Behavior on positionPx {
328− enabled: previewListView.open
329− UbuntuNumberAnimation {}
330− }
331− }
332−
333301 Connections {
334302 target: scopeView.scope
335303 onPreviewReady: {
@@ -348,179 +316,13 @@ ScopeView {
348316 }
349317 }
350318
351− PreviewDelegateMapper {
352− id: previewDelegateMapper
353− }
354−
355− ListView {
356− id: previewListView
357− objectName: "previewListView"
358− height: effect.bottomGapPx - effect.topGapPx
359− anchors {
360− top: parent.top
361− topMargin: effect.topGapPx
362− left: parent.left
363− right: parent.right
364− }
365− orientation: ListView.Horizontal
366− highlightRangeMode: ListView.StrictlyEnforceRange
367− snapMode: ListView.SnapOneItem
368− boundsBehavior: Flickable.DragAndOvershootBounds
369− highlightMoveDuration: 250
370− flickDeceleration: units.gu(625)
371− maximumFlickVelocity: width * 5
372− cacheBuffer: 0
373−
374− // To be set before opening the preview
375− property string categoryId: ""
376− property var categoryDelegate
377−
378− // because the ListView is built asynchronous, setting the
379− // currentIndex directly won't work. We need to refresh it
380− // when the first preview is ready to be displayed.
381− property bool init: true
382−
383− onCurrentIndexChanged: {
384− var row = Math.floor(currentIndex / categoryDelegate.columns);
385− if (categoryDelegate.collapsedRowCount <= row) {
386− categoryView.expandedCategoryId = categoryId
387− }
388−
389− if (open) {
390− categoryDelegate.highlightIndex = currentIndex
391− }
392−
393− if (!init && model !== undefined) {
394− var item = model.get(currentIndex)
395− scopeView.scope.preview( item.uri, item.icon, item.category, 0, item.mimetype, item.title, item.comment, item.dndUri, item.metadata)
396− }
397−
398− var itemY = categoryView.contentItem.mapFromItem(categoryDelegate.currentItem).y;
399− var newContentY = itemY - effect.positionPx - categoryDelegate.verticalSpacing;
400− var effectAdjust = effect.positionPx;
401− if (newContentY < 0) {
402− effectAdjust += newContentY;
403− newContentY = 0;
404− }
405− if (newContentY > Math.max(0, categoryView.contentHeight - categoryView.height)) {
406− effectAdjust += -(categoryView.contentHeight - categoryView.height) + newContentY
407− newContentY = categoryView.contentHeight - categoryView.height;
408− }
409−
410− effect.positionPx = effectAdjust;
411− categoryView.contentY = newContentY - categoryView.originY;
412− }
413−
414− property bool open: false
415− property bool onScreen: false
416−
417− onOpenChanged: {
418− if (open) {
419− onScreen = true;
420− categoryDelegate.highlightIndex = currentIndex;
421− pageHeader.unfocus();
422− } else {
423− // Cancel any pending preview requests or actions
424− if (previewListView.currentItem.previewData !== undefined) {
425− previewListView.currentItem.previewData.cancelAction();
426− }
427− scopeView.scope.cancelActivation();
428− model = undefined;
429− categoryView.correctExpandedCategory();
430− categoryDelegate.highlightIndex = -1;
431− }
432− }
433−
434− Rectangle {
435− anchors.fill: parent
436− color: Qt.rgba(0, 0, 0, .3)
437− z: -1
438− }
439−
440− delegate: Loader {
441− id: previewLoader
442− objectName: "previewLoader" + index
443− height: previewListView.height
444− width: previewListView.width
445− asynchronous: true
446− source: previewListView.onScreen ?
447− (previewData !== undefined ? previewDelegateMapper.map(previewData.rendererName) : "DashPreviewPlaceholder.qml") : ""
448−
449− onPreviewDataChanged: {
450− if (previewData !== undefined && source.toString().indexOf("DashPreviewPlaceholder.qml") != -1) {
451− previewLoader.opacity = 0;
452− }
453− }
454−
455− onSourceChanged: {
456− if (previewData !== undefined) {
457− fadeIn.start()
458− }
459− }
460−
461− PropertyAnimation {
462− id: fadeIn
463− target: previewLoader
464− property: "opacity"
465− from: 0.0
466− to: 1.0
467− duration: UbuntuAnimation.BriskDuration
468− }
469−
470− property var previewData
471− property bool valid: item !== null
472−
473− onLoaded: {
474− if (previewListView.onScreen && previewData !== undefined) {
475− item.previewData = Qt.binding(function() { return previewData })
476− }
477− }
478−
479− Connections {
480− ignoreUnknownSignals: true
481− target: item
482− onClose: {
483− previewListView.open = false
484− }
485− }
486−
487− function closePreviewSpinner() {
488− if(item) {
489− item.showProcessingAction = false;
490− }
491− }
492− }
493− }
494−
495− Image {
496− objectName: "pointerArrow"
497− anchors {
498− top: previewListView.bottom
499− left: parent.left
500− leftMargin: previewListView.categoryDelegate !== undefined && previewListView.categoryDelegate.currentItem ?
501− previewListView.categoryDelegate.currentItem.center + (-width + margins) / 2 : 0
502−
503− Behavior on leftMargin {
504− SmoothedAnimation {
505− duration: UbuntuAnimation.FastDuration
506− }
507− }
508− }
509− height: units.gu(1)
510− width: units.gu(2)
511− property int margins: previewListView.categoryDelegate ? previewListView.categoryDelegate.margins : 0
512− opacity: previewListView.open ? .5 : 0
513−
514− source: "graphics/tooltip_arrow.png"
515− }
516−
517319 // TODO: Move as InverseMouseArea to DashPreview
518320 MouseArea {
519321 objectName: "closePreviewMouseArea"
520322 enabled: previewListView.onScreen
521323 anchors {
522324 fill: parent
523− topMargin: effect.bottomGapPx
325+ topMargin: openEffect.bottomGapPx
524326 }
525327 onClicked: {
526328 previewListView.open = false;
Dash/PreviewListView.qmlnew file+201
@@ -0,0 +1,201 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import Ubuntu.Components 0.1
19+import Unity 0.1
20+import "../Components"
21+
22+Item {
23+ property OpenEffect openEffect: null
24+ property ScopeListView categoryView: null
25+ property Scope scope: null
26+
27+ property alias open: previewListView.open
28+ property alias onScreen: previewListView.onScreen
29+ property alias categoryId: previewListView.categoryId
30+ property alias categoryDelegate: previewListView.categoryDelegate
31+ property alias init: previewListView.init
32+ property alias model: previewListView.model
33+ property alias currentIndex: previewListView.currentIndex
34+ property alias currentItem: previewListView.currentItem
35+
36+ Image {
37+ objectName: "pointerArrow"
38+ anchors {
39+ top: previewListView.bottom
40+ left: parent.left
41+ leftMargin: previewListView.categoryDelegate !== undefined && previewListView.categoryDelegate.currentItem ?
42+ previewListView.categoryDelegate.currentItem.center + (-width + margins) / 2 : 0
43+
44+ Behavior on leftMargin {
45+ SmoothedAnimation {
46+ duration: UbuntuAnimation.FastDuration
47+ }
48+ }
49+ }
50+ height: units.gu(1)
51+ width: units.gu(2)
52+ property int margins: previewListView.categoryDelegate ? previewListView.categoryDelegate.margins : 0
53+ opacity: previewListView.open ? .5 : 0
54+
55+ source: "graphics/tooltip_arrow.png"
56+ }
57+
58+ ListView {
59+ id: previewListView
60+ objectName: "previewListView"
61+ height: openEffect.bottomGapPx - openEffect.topGapPx
62+ anchors {
63+ top: parent.top
64+ topMargin: openEffect.topGapPx
65+ left: parent.left
66+ right: parent.right
67+ }
68+ orientation: ListView.Horizontal
69+ highlightRangeMode: ListView.StrictlyEnforceRange
70+ snapMode: ListView.SnapOneItem
71+ boundsBehavior: Flickable.DragAndOvershootBounds
72+ highlightMoveDuration: 250
73+ flickDeceleration: units.gu(625)
74+ maximumFlickVelocity: width * 5
75+ cacheBuffer: 0
76+
77+ // To be set before opening the preview
78+ property string categoryId: ""
79+ property var categoryDelegate
80+
81+ // because the ListView is built asynchronous, setting the
82+ // currentIndex directly won't work. We need to refresh it
83+ // when the first preview is ready to be displayed.
84+ property bool init: true
85+
86+ PreviewDelegateMapper {
87+ id: previewDelegateMapper
88+ }
89+
90+ onCurrentIndexChanged: {
91+ var row = Math.floor(currentIndex / categoryDelegate.columns);
92+ if (categoryDelegate.collapsedRowCount <= row) {
93+ categoryView.expandedCategoryId = categoryId
94+ }
95+
96+ if (open) {
97+ categoryDelegate.highlightIndex = currentIndex
98+ }
99+
100+ if (!init && model !== undefined) {
101+ var item = model.get(currentIndex)
102+ scope.preview( item.uri, item.icon, item.category, 0, item.mimetype, item.title, item.comment, item.dndUri, item.metadata)
103+ }
104+
105+ var itemY = categoryView.contentItem.mapFromItem(categoryDelegate.currentItem).y;
106+ var newContentY = itemY - openEffect.positionPx - categoryDelegate.verticalSpacing;
107+ var effectAdjust = openEffect.positionPx;
108+ if (newContentY < 0) {
109+ effectAdjust += newContentY;
110+ newContentY = 0;
111+ }
112+ if (newContentY > Math.max(0, categoryView.contentHeight - categoryView.height)) {
113+ effectAdjust += -(categoryView.contentHeight - categoryView.height) + newContentY
114+ newContentY = categoryView.contentHeight - categoryView.height;
115+ }
116+
117+ openEffect.positionPx = effectAdjust;
118+ categoryView.contentY = newContentY - categoryView.originY;
119+ }
120+
121+ property bool open: false
122+ property bool onScreen: false
123+
124+ onOpenChanged: {
125+ if (open) {
126+ onScreen = true;
127+ categoryDelegate.highlightIndex = currentIndex;
128+ pageHeader.unfocus();
129+ } else {
130+ // Cancel any pending preview requests or actions
131+ if (previewListView.currentItem.previewData !== undefined) {
132+ previewListView.currentItem.previewData.cancelAction();
133+ }
134+ scope.cancelActivation();
135+ model = undefined;
136+ categoryView.correctExpandedCategory();
137+ categoryDelegate.highlightIndex = -1;
138+ }
139+ }
140+
141+ Rectangle {
142+ anchors.fill: parent
143+ color: Qt.rgba(0, 0, 0, .3)
144+ z: -1
145+ }
146+
147+ delegate: Loader {
148+ id: previewLoader
149+ objectName: "previewLoader" + index
150+ height: previewListView.height
151+ width: previewListView.width
152+ asynchronous: true
153+ source: previewListView.onScreen ?
154+ (previewData !== undefined ? previewDelegateMapper.map(previewData.rendererName) : "DashPreviewPlaceholder.qml") : ""
155+
156+ onPreviewDataChanged: {
157+ if (previewData !== undefined && source.toString().indexOf("DashPreviewPlaceholder.qml") != -1) {
158+ previewLoader.opacity = 0;
159+ }
160+ }
161+
162+ onSourceChanged: {
163+ if (previewData !== undefined) {
164+ fadeIn.start()
165+ }
166+ }
167+
168+ PropertyAnimation {
169+ id: fadeIn
170+ target: previewLoader
171+ property: "opacity"
172+ from: 0.0
173+ to: 1.0
174+ duration: UbuntuAnimation.BriskDuration
175+ }
176+
177+ property var previewData
178+ property bool valid: item !== null
179+
180+ onLoaded: {
181+ if (previewListView.onScreen && previewData !== undefined) {
182+ item.previewData = Qt.binding(function() { return previewData })
183+ }
184+ }
185+
186+ Connections {
187+ ignoreUnknownSignals: true
188+ target: item
189+ onClose: {
190+ previewListView.open = false
191+ }
192+ }
193+
194+ function closePreviewSpinner() {
195+ if(item) {
196+ item.showProcessingAction = false;
197+ }
198+ }
199+ }
200+ }
201+}
Dash/ScopeView.qml+7−1
@@ -17,17 +17,23 @@
1717import QtQuick 2.0
1818import Utils 0.1
1919import Unity 0.1
20+import "../Components"
2021
2122FocusScope {
2223 property Scope scope
2324 property SortFilterProxyModel categories: categoryFilter
2425 property bool isCurrent
25− property ListModel searchHistory
2626 property bool moving: false
27+ property int tabBarHeight: 0
28+ property PageHeader pageHeader: null
29+ property OpenEffect openEffect: null
30+ property Item previewListView: null
2731
2832 signal endReached
2933 signal movementStarted
3034 signal positionedAtBeginning
35+ signal headerPositionChanged(int position)
36+ signal headerHeightChanged(int height)
3137
3238 // FIXME delay the search so that daemons have time to settle, note that
3339 // removing this will break ScopeView::test_changeScope
Hud/HudParametrizedActionsPage.qml+1−1
@@ -89,7 +89,7 @@ Item {
8989 anchors.left: parent.left
9090 anchors.right: parent.right
9191
92− PageHeader {
92+ PageHeaderLabel {
9393 id: header
9494 anchors.top: parent.top
9595 anchors.left: parent.left
Shell.qml−2
@@ -62,8 +62,6 @@ FocusScope {
6262 }
6363 }
6464
65− property ListModel searchHistory: SearchHistoryModel {}
66−
6765 property var applicationManager: ApplicationManagerWrapper {}
6866
6967 Binding {
tests/qmltests/CMakeLists.txt+1−2
@@ -40,9 +40,8 @@ add_qml_test(Components Stage IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS}
4040 ${CMAKE_BINARY_DIR}/tests/mocks
4141 ${CMAKE_BINARY_DIR}/plugins)
4242add_qml_test(Components Tile)
43−add_qml_test(Components PageHeader)
43+add_qml_test(Components PageHeaderLabel)
4444add_qml_test(Dash Dash IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
45−add_qml_test(Dash DashBar IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${CMAKE_CURRENT_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
4645add_qml_test(Dash DashContent IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
4746add_qml_test(Dash DashPreview)
4847add_qml_test(Dash GenericPreview)
tests/qmltests/Components/tst_PageHeader.qml → tests/qmltests/Components/tst_PageHeaderLabel.qmlrenamed+2−2
@@ -31,7 +31,7 @@ Item {
3131 }
3232
3333 UT.UnityTestCase {
34− name: "PageHeaderTest"
34+ name: "PageHeaderLabelTest"
3535 when: windowShown
3636
3737 property alias searchEnabled : pageHeader.searchEntryEnabled
@@ -164,7 +164,7 @@ Item {
164164 anchors.fill: parent
165165 spacing: units.gu(1)
166166
167− PageHeader {
167+ PageHeaderLabel {
168168 id: pageHeader
169169 anchors {
170170 left: parent.left
tests/qmltests/Dash/qml/FakeScopeView.qml+1
@@ -23,6 +23,7 @@ ScopeView {
2323 id: fakeScopeView
2424
2525 property alias backColor : back.color
26+ property var categoryView: null
2627
2728 onEndReached: {
2829 if (shell != undefined && shell.scopeStatus != undefined) {
tests/qmltests/Dash/tst_Dash.qml+6−8
@@ -25,8 +25,6 @@ Item {
2525 width: units.gu(40)
2626 height: units.gu(80)
2727
28− property ListModel searchHistory : ListModel {}
29−
3028 Dash {
3129 id: dash
3230 anchors.fill: parent
@@ -83,14 +81,14 @@ Item {
8381 // wait for scopes to load
8482 tryCompare(scopes, "loaded", true);
8583
86− var dashbar = findChild(dash, "dashbar");
87− verify(dashbar != undefined)
84+ var tabbar = findChild(dash, "tabbar");
85+ verify(tabbar != undefined)
8886 var dashContent = findChild(dash, "dashContent");
8987 var current_index = dashContent.currentIndex;
9088
9189 dash.setCurrentScope(data.tag, true /* animate */, false /* reset */);
9290 compare(dashContent.currentIndex, data.shouldBeVisible ? data.visualIndex : current_index);
93− compare(dashbar.currentIndex, data.shouldBeVisible ? data.visualIndex : current_index);
91+ compare(tabbar.selectedTabIndex, data.shouldBeVisible ? data.visualIndex : current_index);
9492 }
9593
9694 function test_show_scope_on_load_data() {
@@ -121,12 +119,12 @@ Item {
121119 // wait for scopes to load
122120 tryCompare(scopes, "loaded", true);
123121
124− var dashbar = findChild(dash, "dashbar");
125− verify(dashbar != undefined)
122+ var tabbar = findChild(dash, "tabbar");
123+ verify(tabbar != undefined)
126124 var dashContent = findChild(dash, "dashContent");
127125 var current_index = dashContent.currentIndex;
128126
129− dashbar.itemSelected(data.visualIndex);
127+ tabbar.selectedTabIndex = data.visualIndex;
130128 compare(dashContent.currentIndex, data.shouldBeVisible ? data.visualIndex : current_index);
131129 }
132130 }
tests/qmltests/Dash/tst_DashBar.qmldeleted−111
@@ -1,111 +0,0 @@
1−/*
2− * Copyright 2013 Canonical Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import QtTest 1.0
19−import Unity 0.1
20−import ".."
21−import "../../../Dash"
22−import Ubuntu.Components 0.1
23−import Unity.Test 0.1 as UT
24−
25−Item {
26− width: units.gu(40)
27− height: units.gu(71)
28−
29− DashBar {
30− id: dashBar
31−
32− anchors {
33− left: parent.left
34− right: parent.right
35− bottom: parent.bottom
36− }
37− model: scopes
38− onItemSelected: currentIndex = index
39− }
40−
41− Scopes {
42− id: scopes
43− }
44−
45− SignalSpy {
46− id: signalSpy
47− signalName: "itemSelected"
48− target: dashBar
49− }
50−
51− UT.UnityTestCase {
52− name: "DashBar"
53− when: scopes.loaded
54−
55− property alias model: dashBar.model
56− property alias currentIndex: dashBar.currentIndex
57−
58− readonly property alias lineHeight: dashBar.lineHeight
59− readonly property alias itemSize: dashBar.itemSize
60− readonly property alias iconSize: dashBar.iconSize
61− readonly property var panel: findChild(dashBar, "panel");
62−
63− function initTestCase() {
64− currentIndex = 2
65− }
66−
67− function waitForAnimationToEnd() {
68− compare(panel.animating, true) // check the animation started
69− tryCompare(panel, "animating", false) // wait till the animation ends
70− }
71−
72− function closePanel() {
73− dashBar.finishNavigation()
74− waitForAnimationToEnd()
75− tryCompare(panel, "opened", false)
76− }
77−
78− function openPanel() {
79− dashBar.startNavigation()
80− waitForAnimationToEnd()
81− tryCompare(panel, "opened", true)
82− }
83−
84− function test_navigationAndHide() {
85− openPanel()
86− closePanel()
87− }
88−
89− function test_itemSelected() {
90− openPanel()
91−
92− var row = findChild(dashBar, "row");
93− tryCompareFunction(function(){return row.width > 0;}, true);
94− for (var i = 0; i < model.rowCount(); i++) {
95− // coordinate x in the middle of item with index 'i'
96− var x = row.x + (row.width / model.rowCount()) * i + itemSize / 2
97−
98− // FIXME workaround for a bug in SignalSpy
99− signalSpy.clear()
100−
101− // (item, x, y, button, modifiers, delay)
102− mouseClick(panel, x, row.height / 2, Qt.LeftButton, Qt.NoModifier, 0)
103− compare(signalSpy.count > 0, true, "signal itemSelected not triggered")
104− compare(signalSpy.signalArguments[0][0], i, "signal itemSelected emitted unexpected index");
105− tryCompare(dashBar, "currentIndex", i);
106− }
107−
108− closePanel()
109− }
110− }
111−}
tests/qmltests/Dash/tst_DashContent.qml+23−3
@@ -26,8 +26,6 @@ Item {
2626 width: units.gu(40)
2727 height: units.gu(80)
2828
29− property ListModel searchHistory: ListModel {}
30−
3129 property var scopeStatus: {
3230 'MockScope1': { 'movementStarted': 0, 'positionedAtBeginning': 0 },
3331 'MockScope2': { 'movementStarted': 0, 'positionedAtBeginning': 0 },
@@ -36,6 +34,12 @@ Item {
3634 'MockScope5': { 'movementStarted': 0, 'positionedAtBeginning': 0 }
3735 }
3836
37+ Item {
38+ // make PageHeader happy
39+ id: greeter
40+ signal shownChanged
41+ }
42+
3943 Scopes {
4044 id: scopesModel
4145 }
@@ -157,7 +161,7 @@ Item {
157161 var dashContentList = findChild(dashContent, "dashContentList");
158162 verify(dashContentList != undefined);
159163
160− tryCompare(scopeLoadedSpy, "count", 5);
164+ tryCompareFunction(function() {return dashContentList.currentItem.item !== null;}, true);
161165 dashContent.setCurrentScopeAtIndex(0, true, false);
162166 dashContentList.currentItem.item.endReached();
163167
@@ -229,5 +233,21 @@ Item {
229233 tryCompare(scopesModel.get(1), "isActive", data.active1);
230234 tryCompare(scopesModel.get(2), "isActive", data.active2);
231235 }
236+
237+ function test_tabBar_index_change() {
238+ compare(dashContent.currentIndex, 0);
239+ mouseClick(dashContent, units.gu(5), units.gu(5))
240+ wait(1000);
241+ mouseClick(dashContent, units.gu(20), units.gu(5))
242+ compare(dashContent.currentIndex, 1);
243+ }
244+
245+ function test_tabBar_listens_to_index_change() {
246+ var tabbar = findChild(dashContent, "tabbar");
247+ tryCompare(dashContent, "currentIndex", 0);
248+ compare(tabbar.selectedTabIndex, 0);
249+ dashContent.currentIndex = 1;
250+ compare(tabbar.selectedTabIndex, 1);
251+ }
232252 }
233253}
tests/qmltests/Dash/tst_GenericScopeView.qml+43−20
@@ -19,6 +19,7 @@ import QtTest 1.0
1919import Unity 0.1
2020import ".."
2121import "../../../Dash"
22+import "../../../Components"
2223import Ubuntu.Components 0.1
2324import Unity.Test 0.1 as UT
2425
@@ -40,50 +41,77 @@ Item {
4041 signal mainStageFocusedApplicationChanged()
4142 }
4243
44+ PreviewListView {
45+ id: previewListView
46+ anchors.fill: parent
47+ openEffect: openEffect
48+ categoryView: genericScopeView
49+ scope: genericScopeView.scope
50+ }
51+
52+ DashContentOpenEffect {
53+ id: openEffect
54+ previewListView: previewListView
55+ }
56+
57+ PageHeaderLabel {
58+ id: pageHeader
59+ }
60+
4361 GenericScopeView {
4462 id: genericScopeView
4563 anchors.fill: parent
64+ previewListView: previewListView;
65+ openEffect: openEffect;
66+ pageHeader: pageHeader;
4667
4768 UT.UnityTestCase {
4869 name: "GenericScopeView"
4970 when: scopes.loaded
5071
51− function test_isCurrent() {
52− var pageHeader = findChild(genericScopeView, "pageHeader");
53− var previewListView = findChild(genericScopeView, "previewListView");
72+ function test_isCurrent() {
5473 genericScopeView.isCurrent = true
5574 pageHeader.searchQuery = "test"
5675 previewListView.open = true
5776 genericScopeView.isCurrent = false
5877 tryCompare(pageHeader, "searchQuery", "")
59− tryCompare(genericScopeView, "previewShown", false);
78+ tryCompare(previewListView, "open", false);
6079 }
6180
6281 function test_showDash() {
63− var previewListView = findChild(genericScopeView, "previewListView");
6482 previewListView.open = true;
6583 scopes.get(0).showDash();
66− tryCompare(genericScopeView, "previewShown", false);
84+ tryCompare(previewListView, "open", false);
6785 }
6886
6987 function test_hideDash() {
70− var previewListView = findChild(genericScopeView, "previewListView");
7188 previewListView.open = true;
7289 scopes.get(0).hideDash();
73− tryCompare(genericScopeView, "previewShown", false);
90+ tryCompare(previewListView, "open", false);
7491 }
7592
7693 function openPreview() {
94+ tryCompareFunction(function() {
95+ var tile = findChild(genericScopeView, "delegate0");
96+ return tile != undefined;
97+ },
98+ true);
7799 var tile = findChild(genericScopeView, "delegate0");
100+ wait(1000);
78101 mouseClick(tile, tile.width / 2, tile.height / 2);
79− var openEffect = findChild(genericScopeView, "openEffect");
102+ tryCompare(previewListView, "open", true);
80103 tryCompare(openEffect, "gap", 1);
81104 }
82105
83106 function checkArrowPosition(index) {
107+ tryCompareFunction(function() {
108+ var tile = findChild(genericScopeView, "delegate" + index);
109+ return tile != undefined;
110+ },
111+ true);
84112 var tile = findChild(genericScopeView, "delegate" + index);
85113 var tileCenter = tile.x + tile.width/2;
86− var pointerArrow = findChild(genericScopeView, "pointerArrow");
114+ var pointerArrow = findChild(previewListView, "pointerArrow");
87115 var pointerArrowCenter = pointerArrow.x + pointerArrow.width/2;
88116 compare(pointerArrowCenter, tileCenter, "Pointer did not move to tile");
89117 }
@@ -92,9 +120,7 @@ Item {
92120 var closePreviewMouseArea = findChild(genericScopeView, "closePreviewMouseArea");
93121 mouseClick(closePreviewMouseArea, closePreviewMouseArea.width / 2, closePreviewMouseArea.height / 2);
94122
95− var previewListView = findChild(genericScopeView, "previewListView");
96123 tryCompare(previewListView, "open", false);
97− var openEffect = findChild(genericScopeView, "openEffect");
98124 tryCompare(openEffect, "gap", 0);
99125
100126 var categoryListView = findChild(genericScopeView, "categoryListView");
@@ -103,13 +129,12 @@ Item {
103129 }
104130
105131 function test_previewOpenClose() {
106− var previewListView = findChild(genericScopeView, "previewListView");
107132 tryCompare(previewListView, "open", false);
108133
109134 openPreview();
110135
111136 // check for it opening successfully
112− var currentPreviewItem = findChild(genericScopeView, "previewLoader0");
137+ var currentPreviewItem = findChild(previewListView, "previewLoader0");
113138 tryCompareFunction(function() {
114139 var parts = currentPreviewItem.source.toString().split("/");
115140 var name = parts[parts.length - 1];
@@ -130,13 +155,12 @@ Item {
130155 }
131156
132157 function test_previewCycleOne() {
133− var previewListView = findChild(genericScopeView, "previewListView");
134158 tryCompare(previewListView, "open", false);
135159
136160 openPreview();
137161
138162 // wait for it to be loaded
139− var currentPreviewItem = findChild(genericScopeView, "previewLoader0");
163+ var currentPreviewItem = findChild(previewListView, "previewLoader0");
140164 tryCompareFunction(function() {
141165 var parts = currentPreviewItem.source.toString().split("/");
142166 var name = parts[parts.length - 1];
@@ -158,7 +182,7 @@ Item {
158182 previewListView.height / 2);
159183
160184 // wait for it to be loaded
161− var nextPreviewItem = findChild(genericScopeView, "previewLoader" + i);
185+ var nextPreviewItem = findChild(previewListView, "previewLoader" + i);
162186 tryCompareFunction(function() {
163187 var parts = nextPreviewItem.source.toString().split("/");
164188 var name = parts[parts.length - 1];
@@ -176,11 +200,10 @@ Item {
176200
177201 function test_show_spinner() {
178202 openPreview();
179− var previewListView = findChild(genericScopeView, "previewListView");
180− var previewLoader = findChild(genericScopeView, "previewLoader0");
203+ var previewLoader = findChild(previewListView, "previewLoader0");
181204
182205 previewLoader.item.showProcessingAction = true;
183− var waitingForAction = findChild(genericScopeView, "waitingForActionMouseArea");
206+ var waitingForAction = findChild(previewListView, "waitingForActionMouseArea");
184207 tryCompare(waitingForAction, "enabled", true);
185208 previewLoader.closePreviewSpinner();
186209 tryCompare(waitingForAction, "enabled", false);