on xenial help sign in
Refactor dash activity indicator. Fixes: 1351539
c853a12Michał Sawicz
plugins/Dash/ScopeStyle.qml+11−8
@@ -38,14 +38,20 @@ QtObject {
38 /// Color used for text and symbolic icons
38 /// Color used for text and symbolic icons
39 readonly property color foreground: "foreground-color" in style ? style["foreground-color"] : d.defaultDark
39 readonly property color foreground: "foreground-color" in style ? style["foreground-color"] : d.defaultDark
40
40
41+ /// Luminance of the foreground color
42+ readonly property real foregroundLuminance: foreground ? luminance(foreground) : d.defaultForegroundLuminance
43+
41 /// Color used for the overall background
44 /// Color used for the overall background
42 readonly property color background: "background-color" in style ? style["background-color"] : "transparent"
45 readonly property color background: "background-color" in style ? style["background-color"] : "transparent"
43
46
47+ /// Luminance of the foreground color
48+ readonly property real backgroundLuminance: background ? luminance(background) : d.defaultBackgroundLuminance
49+
44 /*! \brief Luminance threshold for switching between fore and background color
50 /*! \brief Luminance threshold for switching between fore and background color
45
51
46 \note If background colour is not fully opaque, it's not taken into account.
52 \note If background colour is not fully opaque, it's not taken into account.
47 */
53 */
48− readonly property real threshold: background.a !== 1.0 ? d.foregroundLuminance : (d.foregroundLuminance + d.backgroundLuminance) / 2
54+ readonly property real threshold: background.a !== 1.0 ? foregroundLuminance : (foregroundLuminance + backgroundLuminance) / 2
49
55
50 /*! \brief The lighter of foreground and background colors
56 /*! \brief The lighter of foreground and background colors
51
57
@@ -53,8 +59,8 @@ QtObject {
53 and defaults to the theme-provided light color.
59 and defaults to the theme-provided light color.
54 */
60 */
55 readonly property color light: {
61 readonly property color light: {
56− if (background.a !== 1.0) return d.foregroundLuminance > d.defaultLightLuminance ? foreground : d.defaultLight;
62+ if (background.a !== 1.0) return foregroundLuminance > d.defaultLightLuminance ? foreground : d.defaultLight;
57− return d.foregroundLuminance > d.backgroundLuminance ? foreground : background;
63+ return foregroundLuminance > backgroundLuminance ? foreground : background;
58 }
64 }
59
65
60 /*! \brief The darker of foreground and background colors
66 /*! \brief The darker of foreground and background colors
@@ -63,8 +69,8 @@ QtObject {
63 and defaults to the theme-provided dark color.
69 and defaults to the theme-provided dark color.
64 */
70 */
65 readonly property color dark: {
71 readonly property color dark: {
66− if (background.a !== 1.0) return d.foregroundLuminance < d.defaultDarkLuminance ? foreground : d.defaultDark;
72+ if (background.a !== 1.0) return foregroundLuminance < d.defaultDarkLuminance ? foreground : d.defaultDark;
67− return d.foregroundLuminance < d.backgroundLuminance ? foreground : background;
73+ return foregroundLuminance < backgroundLuminance ? foreground : background;
68 }
74 }
69
75
70 /// Source of the logo image for the header
76 /// Source of the logo image for the header
@@ -78,9 +84,6 @@ QtObject {
78
84
79 //! @cond
85 //! @cond
80 property var d: QtObject {
86 property var d: QtObject {
81− readonly property real foregroundLuminance: luminance(foreground)
82− readonly property real backgroundLuminance: luminance(background)
83−
84 // FIXME: should be taken from the theme
87 // FIXME: should be taken from the theme
85 readonly property color defaultLight: "white"
88 readonly property color defaultLight: "white"
86 readonly property color defaultDark: "grey"
89 readonly property color defaultDark: "grey"
qml/Components/PageHeader.qml+1−14
@@ -32,7 +32,6 @@ Item {
32 property bool searchEntryEnabled: false
32 property bool searchEntryEnabled: false
33 property ListModel searchHistory: SearchHistoryModel
33 property ListModel searchHistory: SearchHistoryModel
34 property alias searchQuery: searchTextField.text
34 property alias searchQuery: searchTextField.text
35− property bool searchInProgress: false
36
35
37 property alias bottomItem: bottomContainer.children
36 property alias bottomItem: bottomContainer.children
38 property int paginationCount: 0
37 property int paginationCount: 0
@@ -203,25 +202,13 @@ Item {
203 anchors.fill: parent
202 anchors.fill: parent
204 anchors.margins: units.gu(.75)
203 anchors.margins: units.gu(.75)
205 source: "image://theme/clear"
204 source: "image://theme/clear"
206− opacity: searchTextField.text.length > 0 && !searchActivityIndicator.running
205+ opacity: searchTextField.text.length > 0
207 visible: opacity > 0
206 visible: opacity > 0
208 Behavior on opacity {
207 Behavior on opacity {
209 UbuntuNumberAnimation { duration: UbuntuAnimation.FastDuration }
208 UbuntuNumberAnimation { duration: UbuntuAnimation.FastDuration }
210 }
209 }
211 }
210 }
212
211
213− ActivityIndicator {
214− id: searchActivityIndicator
215− objectName: "searchIndicator"
216− anchors.fill: parent
217− anchors.margins: units.gu(.75)
218− running: root.searchInProgress
219− opacity: running ? 1 : 0
220− Behavior on opacity {
221− UbuntuNumberAnimation { duration: UbuntuAnimation.FastDuration }
222− }
223− }
224−
225 onClicked: {
212 onClicked: {
226 root.resetSearch(true);
213 root.resetSearch(true);
227 root.openSearchHistory();
214 root.openSearchHistory();
qml/Dash/Dash.qml+68−1
@@ -14,7 +14,7 @@
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
15 */
16
16
17−import QtQuick 2.0
17+import QtQuick 2.2
18import Ubuntu.Components 0.1
18import Ubuntu.Components 0.1
19import Unity 0.2
19import Unity 0.2
20import Utils 0.1
20import Utils 0.1
@@ -140,4 +140,71 @@ Showable {
140 }
140 }
141 }
141 }
142 }
142 }
143+
144+ Rectangle {
145+ id: indicator
146+ objectName: "processingIndicator"
147+ anchors {
148+ left: parent.left
149+ right: parent.right
150+ bottom: parent.bottom
151+ }
152+ height: units.dp(3)
153+ color: scopeStyle.backgroundLuminance > 0.7 ? "#50000000" : "#50ffffff"
154+ opacity: 0
155+ visible: opacity > 0
156+
157+ readonly property bool processing: dashContent.processing || scopeItem.processing
158+
159+ Behavior on opacity {
160+ UbuntuNumberAnimation { duration: UbuntuAnimation.FastDuration }
161+ }
162+
163+ onProcessingChanged: {
164+ if (processing) delay.start();
165+ else if (!persist.running) indicator.opacity = 0;
166+ }
167+
168+ Timer {
169+ id: delay
170+ interval: 200
171+ onTriggered: if (indicator.processing) {
172+ persist.restart();
173+ indicator.opacity = 1;
174+ }
175+ }
176+
177+ Timer {
178+ id: persist
179+ interval: 2 * UbuntuAnimation.SleepyDuration - UbuntuAnimation.FastDuration
180+ onTriggered: if (!indicator.processing) indicator.opacity = 0
181+ }
182+
183+ Rectangle {
184+ id: orange
185+ anchors { top: parent.top; bottom: parent.bottom }
186+ width: parent.width / 4
187+ color: Theme.palette.selected.foreground
188+
189+ SequentialAnimation {
190+ running: indicator.visible
191+ loops: Animation.Infinite
192+ XAnimator {
193+ from: -orange.width / 2
194+ to: indicator.width - orange.width / 2
195+ duration: UbuntuAnimation.SleepyDuration
196+ easing.type: Easing.InOutSine
197+ target: orange
198+ }
199+ XAnimator {
200+ from: indicator.width - orange.width / 2
201+ to: -orange.width / 2
202+ duration: UbuntuAnimation.SleepyDuration
203+ easing.type: Easing.InOutSine
204+ target: orange
205+ }
206+ }
207+ }
208+ }
209+
143}
210}
qml/Dash/DashContent.qml+2
@@ -25,6 +25,8 @@ Item {
25
25
26 property alias scopes: dashContentList.model
26 property alias scopes: dashContentList.model
27 readonly property alias currentIndex: dashContentList.currentIndex
27 readonly property alias currentIndex: dashContentList.currentIndex
28+ readonly property bool processing: dashContentList.currentItem && dashContentList.currentItem.item
29+ && dashContentList.currentItem.item.processing || false
28
30
29 signal scopeLoaded(string scopeId)
31 signal scopeLoaded(string scopeId)
30 signal gotoScope(string scopeId)
32 signal gotoScope(string scopeId)
qml/Dash/DashNavigationList.qml−5
@@ -35,11 +35,6 @@ Item {
35 anchors.fill: parent
35 anchors.fill: parent
36 }
36 }
37
37
38− ActivityIndicator {
39− id: loadingIndicator
40− anchors.centerIn: parent
41− running: !(navigation && navigation.loaded)
42− }
43 clip: true
38 clip: true
44
39
45 Behavior on height {
40 Behavior on height {
qml/Dash/GenericScopeView.qml+2−1
@@ -40,6 +40,8 @@ FocusScope {
40 style: scope ? scope.customizations : {}
40 style: scope ? scope.customizations : {}
41 }
41 }
42
42
43+ readonly property bool processing: scope ? scope.searchInProgress || previewListView.processing : false
44+
43 signal backClicked()
45 signal backClicked()
44
46
45 function positionAtBeginning() {
47 function positionAtBeginning() {
@@ -385,7 +387,6 @@ FocusScope {
385 title: scopeView.scope ? scopeView.scope.name : ""
387 title: scopeView.scope ? scopeView.scope.name : ""
386 showBackButton: scopeView.hasBackAction
388 showBackButton: scopeView.hasBackAction
387 searchEntryEnabled: true
389 searchEntryEnabled: true
388− searchInProgress: scopeView.scope ? scopeView.scope.searchInProgress : false
389 scopeStyle: scopeView.scopeStyle
390 scopeStyle: scopeView.scopeStyle
390
391
391 bottomItem: DashNavigation {
392 bottomItem: DashNavigation {
qml/Dash/PreviewListView.qml+23−27
@@ -32,6 +32,9 @@ Item {
32 property alias currentItem: previewListView.currentItem
32 property alias currentItem: previewListView.currentItem
33 property alias count: previewListView.count
33 property alias count: previewListView.count
34
34
35+ readonly property bool processing: currentItem && (!currentItem.previewModel.loaded
36+ || currentItem.previewModel.processingAction)
37+
35 PageHeader {
38 PageHeader {
36 id: header
39 id: header
37 objectName: "pageHeader"
40 objectName: "pageHeader"
@@ -83,39 +86,32 @@ Item {
83 }
86 }
84 }
87 }
85
88
86− delegate: Item {
89+ delegate: Previews.Preview {
90+ id: preview
87− objectName: "previewItem" + index
91+ objectName: "preview" + index
88 height: previewListView.height
92 height: previewListView.height
89 width: previewListView.width
93 width: previewListView.width
90
94
91− readonly property bool ready: preview.previewModel.loaded
95+ isCurrent: ListView.isCurrentItem
92−
93− Previews.Preview {
94− id: preview
95− objectName: "preview" + index
96− anchors.fill: parent
97
96
98− isCurrent: parent.ListView.isCurrentItem
99−
100− previewModel: {
97+ previewModel: {
101− var previewStack = root.scope.preview(result);
98+ var previewStack = root.scope.preview(result);
102− return previewStack.getPreviewModel(0);
99+ return previewStack.getPreviewModel(0);
103− }
104− scopeStyle: root.scopeStyle
105 }
100 }
101+ scopeStyle: root.scopeStyle
102+ }
103+ }
106
104
107− MouseArea {
105+ MouseArea {
108− id: processingMouseArea
106+ id: processingMouseArea
109− objectName: "processingMouseArea"
107+ objectName: "processingMouseArea"
110− anchors.fill: parent
108+ anchors {
111− enabled: !preview.previewModel.loaded || preview.previewModel.processingAction
109+ left: parent.left
112−
110+ right: parent.right
113− ActivityIndicator {
111+ top: pageHeader.bottom
114− anchors.centerIn: parent
112+ bottom: parent.bottom
115− visible: root.open && parent.enabled
116− running: visible
117− }
118− }
119 }
113 }
114+
115+ enabled: root.processing
120 }
116 }
121}
117}
tests/autopilot/unity8/shell/emulators/dash.py+1−1
@@ -150,7 +150,7 @@ class Dash(emulators.UnityEmulatorBase):
150 headerContainer.contentY.wait_for(0)
150 headerContainer.contentY.wait_for(0)
151 search_text_field = self._get_search_text_field()
151 search_text_field = self._get_search_text_field()
152 search_text_field.write(query)
152 search_text_field.write(query)
153− current_header.select_single(objectName="searchIndicator").running.wait_for(False)
153+ self.select_single(objectName="processingIndicator").visible.wait_for(False)
154
154
155 def _get_search_text_field(self):
155 def _get_search_text_field(self):
156 page_header = self._get_current_page_header()
156 page_header = self._get_current_page_header()
tests/plugins/Dash/tst_ScopeStyle.qml+14−7
@@ -54,6 +54,9 @@ Rectangle {
54 { tag: "lightgrey", luminance: 0.8275 },
54 { tag: "lightgrey", luminance: 0.8275 },
55 { tag: "grey", luminance: 0.5020 },
55 { tag: "grey", luminance: 0.5020 },
56 { tag: "darkgrey", luminance: 0.6627 },
56 { tag: "darkgrey", luminance: 0.6627 },
57+ { tag: "red", luminance: 0.2126 },
58+ { tag: "green", luminance: 0.3590 },
59+ { tag: "blue", luminance: 0.0722 },
57 ];
60 ];
58 }
61 }
59
62
@@ -64,10 +67,10 @@ Rectangle {
64
67
65 function test_foreground_data() {
68 function test_foreground_data() {
66 return [
69 return [
67− { tag: "default", index: 0, foreground: "grey" },
70+ { tag: "default", index: 0, foreground: "grey", luminance: 0.5020 },
68− { tag: "red on black", index: 1, foreground: "red" },
71+ { tag: "red on black", index: 1, foreground: "red", luminance: 0.2126 },
69− { tag: "green on white", index: 2, foreground: "green" },
72+ { tag: "green on white", index: 2, foreground: "green", luminance: 0.3590 },
70− { tag: "blue on darkgrey", index: 3, foreground: "blue" },
73+ { tag: "blue on darkgrey", index: 3, foreground: "blue", luminance: 0.0722 },
71 ];
74 ];
72 }
75 }
73
76
@@ -75,14 +78,15 @@ Rectangle {
75 tool.style = testCase.styles[data.index];
78 tool.style = testCase.styles[data.index];
76 verify(Qt.colorEqual(tool.foreground, data.foreground),
79 verify(Qt.colorEqual(tool.foreground, data.foreground),
77 "Foreground color not equal: %1 != %2".arg(tool.foreground).arg(data.foreground));
80 "Foreground color not equal: %1 != %2".arg(tool.foreground).arg(data.foreground));
81+ compare(tool.foregroundLuminance.toFixed(4), data.luminance.toFixed(4));
78 }
82 }
79
83
80 function test_background_data() {
84 function test_background_data() {
81 return [
85 return [
82 { tag: "default", index: 0, background: "transparent" },
86 { tag: "default", index: 0, background: "transparent" },
83− { tag: "red on black", index: 1, background: "black" },
87+ { tag: "red on black", index: 1, background: "black", luminance: 0 },
84− { tag: "green on white", index: 2, background: "white" },
88+ { tag: "green on white", index: 2, background: "white", luminance: 1 },
85− { tag: "blue on darkgrey", index: 3, background: "darkgrey" },
89+ { tag: "blue on darkgrey", index: 3, background: "darkgrey", luminance: 0.6627 },
86 ];
90 ];
87 }
91 }
88
92
@@ -90,6 +94,9 @@ Rectangle {
90 tool.style = testCase.styles[data.index];
94 tool.style = testCase.styles[data.index];
91 verify(Qt.colorEqual(tool.background, data.background),
95 verify(Qt.colorEqual(tool.background, data.background),
92 "Background color not equal: %1 != %2".arg(tool.background).arg(data.background));
96 "Background color not equal: %1 != %2".arg(tool.background).arg(data.background));
97+ if (data.hasOwnProperty("luminance")) {
98+ compare(tool.backgroundLuminance.toFixed(4), data.luminance.toFixed(4));
99+ }
93 }
100 }
94
101
95 function test_threshold_data() {
102 function test_threshold_data() {
tests/qmltests/Components/tst_PageHeader.qml−23
@@ -119,29 +119,6 @@ Item {
119 compare(pageHeader.searchHistory.get(0).query, "humppa4")
119 compare(pageHeader.searchHistory.get(0).query, "humppa4")
120 }
120 }
121
121
122− function test_search_indicator() {
123− var searchIndicator = findChild(pageHeader, "searchIndicator")
124− var clearIcon = findChild(pageHeader, "clearIcon")
125−
126− pageHeader.triggerSearch()
127−
128− tryCompare(clearIcon, "visible", false)
129− pageHeader.searchQuery = "ubuntu"
130− tryCompare(clearIcon, "visible", true)
131−
132− pageHeader.searchInProgress = false
133− compare(searchIndicator.running, false, "Search indicator is running.")
134− tryCompare(clearIcon, "visible", true)
135−
136− pageHeader.searchInProgress = true
137− compare(searchIndicator.running, true, "Search indicator isn't running.")
138− tryCompare(clearIcon, "visible", false)
139−
140− pageHeader.searchInProgress = false;
141− compare(searchIndicator.running, false, "Search indicator is running.")
142− tryCompare(clearIcon, "visible", true)
143− }
144−
145 function test_titleImage() {
122 function test_titleImage() {
146
123
147 var titleImage = findChild(pageHeader, "titleImage");
124 var titleImage = findChild(pageHeader, "titleImage");
tests/qmltests/Dash/tst_Dash.qml+23−6
@@ -45,12 +45,8 @@ Item {
45 name: "Dash"
45 name: "Dash"
46 when: windowShown
46 when: windowShown
47
47
48− property var scopes
49−
50− Component.onCompleted: {
51− var dashContent = findChild(dash, "dashContent");
48+ readonly property Item dashContent: findChild(dash, "dashContent");
52− scopes = dashContent.scopes;
49+ readonly property var scopes: dashContent.scopes
53− }
54
50
55 function init() {
51 function init() {
56 // clear and reload the scopes.
52 // clear and reload the scopes.
@@ -106,5 +102,26 @@ Item {
106 dashCommunicatorService.mockSetCurrentScope("clickscope", true, true);
102 dashCommunicatorService.mockSetCurrentScope("clickscope", true, true);
107 tryCompare(dashContentList, "currentIndex", 1)
103 tryCompare(dashContentList, "currentIndex", 1)
108 }
104 }
105+
106+ function test_processing_indicator() {
107+ tryCompare(scopes, "loaded", true);
108+
109+ var processingIndicator = findChild(dash, "processingIndicator");
110+ verify(processingIndicator, "Can't find the processing indicator.");
111+
112+ verify(!processingIndicator.visible, "Processing indicator should be visible.");
113+
114+ tryCompareFunction(function() {
115+ return scopes.getScope(dashContent.currentIndex) != null;
116+ }, true);
117+ var currentScope = scopes.getScope(dashContent.currentIndex);
118+ verify(currentScope, "Can't find the current scope.");
119+
120+ currentScope.setSearchInProgress(true);
121+ tryCompare(processingIndicator, "visible", true);
122+
123+ currentScope.setSearchInProgress(false);
124+ tryCompare(processingIndicator, "visible", false);
125+ }
109 }
126 }
110}
127}
tests/qmltests/Dash/tst_GenericScopeView.qml+1−1
@@ -320,7 +320,7 @@ Item {
320 units.gu(2),
320 units.gu(2),
321 testCase.previewListView.height / 2);
321 testCase.previewListView.height / 2);
322 tryCompare(previewListViewList, "moving", false);
322 tryCompare(previewListViewList, "moving", false);
323− tryCompare(testCase.previewListView.currentItem, "objectName", "previewItem" + i);
323+ tryCompare(testCase.previewListView.currentItem, "objectName", "preview" + i);
324
324
325 }
325 }
326 closePreview();
326 closePreview();