on xenial help sign in
initial take on keyboard navigation support
687f068Michael Zanetti
qml/Launcher/Drawer.qml+80−4
@@ -20,6 +20,7 @@ import Unity.Launcher 0.1
20import Utils 0.1
20import Utils 0.1
21import "../Components"
21import "../Components"
22import Qt.labs.settings 1.0
22import Qt.labs.settings 1.0
23+import GSettings 1.0
23
24
24FocusScope {
25FocusScope {
25 id: root
26 id: root
@@ -43,6 +44,12 @@ FocusScope {
43 searchField.focus = true;
44 searchField.focus = true;
44 }
45 }
45
46
47+ Keys.onPressed: {
48+ // Catch all presses here in case the navigation lets something through
49+ // We never want to end up in the launcher with focus
50+ event.accepted = true;
51+ }
52+
46 Settings {
53 Settings {
47 property alias selectedTab: sections.selectedIndex
54 property alias selectedTab: sections.selectedIndex
48 }
55 }
@@ -79,6 +86,9 @@ FocusScope {
79 anchors { left: parent.left; top: parent.top; right: parent.right; margins: units.gu(1) }
86 anchors { left: parent.left; top: parent.top; right: parent.right; margins: units.gu(1) }
80 placeholderText: i18n.tr("Search…")
87 placeholderText: i18n.tr("Search…")
81 focus: true
88 focus: true
89+
90+ KeyNavigation.down: sections
91+
82 onAccepted: {
92 onAccepted: {
83 if (searchField.displayText != "" && listLoader.item && listLoader.item.currentItem) {
93 if (searchField.displayText != "" && listLoader.item && listLoader.item.currentItem) {
84 root.applicationSelected(listLoader.item.getFirstAppId());
94 root.applicationSelected(listLoader.item.getFirstAppId());
@@ -96,13 +106,19 @@ FocusScope {
96 Sections {
106 Sections {
97 id: sections
107 id: sections
98 width: parent.width
108 width: parent.width
109+
110+ KeyNavigation.up: searchField
111+ KeyNavigation.down: headerFocusScope
112+ KeyNavigation.backtab: searchField
113+ KeyNavigation.tab: headerFocusScope
114+
99 actions: [
115 actions: [
100 Action {
116 Action {
101 text: i18n.ctr("Apps sorted alphabetically", "A-Z")
117 text: i18n.ctr("Apps sorted alphabetically", "A-Z")
102 // TODO: Disabling this for now as we don't get the right input from u-a-l yet.
118 // TODO: Disabling this for now as we don't get the right input from u-a-l yet.
103−// },
119+ },
104−// Action {
120+ Action {
105−// text: i18n.ctr("Most used apps", "Most used")
121+ text: i18n.ctr("Most used apps", "Most used")
106 }
122 }
107 ]
123 ]
108
124
@@ -115,9 +131,42 @@ FocusScope {
115 }
131 }
116 }
132 }
117
133
134+ FocusScope {
135+ id: headerFocusScope
136+ KeyNavigation.up: sections
137+ KeyNavigation.down: listLoader.item
138+ KeyNavigation.backtab: sections
139+ KeyNavigation.tab: listLoader.item
140+
141+ GSettings {
142+ id: settings
143+ schema.id: "com.canonical.Unity8"
144+ }
145+
146+ Keys.onPressed: {
147+ switch (event.key) {
148+ case Qt.Key_Return:
149+ case Qt.Key_Enter:
150+ case Qt.Key_Space:
151+ trigger();
152+ event.accepted = true;
153+ }
154+ }
155+
156+ function trigger() {
157+ Qt.openUrlExternally(settings.appstoreUri)
158+ }
159+ }
160+
118 Loader {
161 Loader {
119 id: listLoader
162 id: listLoader
120 anchors { left: parent.left; top: sectionsContainer.bottom; right: parent.right; bottom: parent.bottom; leftMargin: units.gu(1); rightMargin: units.gu(1) }
163 anchors { left: parent.left; top: sectionsContainer.bottom; right: parent.right; bottom: parent.bottom; leftMargin: units.gu(1); rightMargin: units.gu(1) }
164+
165+ KeyNavigation.up: headerFocusScope
166+ KeyNavigation.down: searchField
167+ KeyNavigation.backtab: headerFocusScope
168+ KeyNavigation.tab: searchField
169+
121 sourceComponent: {
170 sourceComponent: {
122 switch (sections.selectedIndex) {
171 switch (sections.selectedIndex) {
123 case 0: return aToZComponent;
172 case 0: return aToZComponent;
@@ -167,10 +216,13 @@ FocusScope {
167 Component {
216 Component {
168 id: mostUsedComponent
217 id: mostUsedComponent
169 DrawerListView {
218 DrawerListView {
219+ id: mostUsedListView
170
220
171 header: MoreAppsHeader {
221 header: MoreAppsHeader {
172 width: parent.width
222 width: parent.width
173 height: units.gu(6)
223 height: units.gu(6)
224+ highlighted: headerFocusScope.activeFocus
225+ onClicked: headerFocusScope.trigger();
174 }
226 }
175
227
176 model: AppDrawerProxyModel {
228 model: AppDrawerProxyModel {
@@ -196,6 +248,9 @@ FocusScope {
196 bottomMargin: units.gu(1)
248 bottomMargin: units.gu(1)
197 clip: true
249 clip: true
198
250
251+ interactive: true
252+ focus: index == mostUsedListView.currentIndex
253+
199 model: sortProxyModel
254 model: sortProxyModel
200
255
201 delegateWidth: units.gu(8)
256 delegateWidth: units.gu(8)
@@ -209,10 +264,13 @@ FocusScope {
209 Component {
264 Component {
210 id: aToZComponent
265 id: aToZComponent
211 DrawerListView {
266 DrawerListView {
267+ id: aToZListView
212
268
213 header: MoreAppsHeader {
269 header: MoreAppsHeader {
214 width: parent.width
270 width: parent.width
215 height: units.gu(6)
271 height: units.gu(6)
272+ highlighted: headerFocusScope.activeFocus
273+ onClicked: headerFocusScope.trigger();
216 }
274 }
217
275
218 model: AppDrawerProxyModel {
276 model: AppDrawerProxyModel {
@@ -244,7 +302,11 @@ FocusScope {
244 anchors { left: parent.left; top: categoryNameLabel.bottom; right: parent.right; topMargin: units.gu(1) }
302 anchors { left: parent.left; top: categoryNameLabel.bottom; right: parent.right; topMargin: units.gu(1) }
245 height: rows * delegateHeight
303 height: rows * delegateHeight
246
304
247− interactive: false
305+ interactive: true
306+ focus: index == aToZListView.currentIndex
307+ onFocusChanged: print("drawer grid", index, "has focus", focus)
308+
309+ onCurrentIndexChanged: print("grid currentIndex changed", currentIndex)
248
310
249 model: AppDrawerProxyModel {
311 model: AppDrawerProxyModel {
250 id: categoryModel
312 id: categoryModel
@@ -263,10 +325,13 @@ FocusScope {
263 Component {
325 Component {
264 id: drawerDelegateComponent
326 id: drawerDelegateComponent
265 AbstractButton {
327 AbstractButton {
328+ id: drawerDelegate
266 width: GridView.view.cellWidth
329 width: GridView.view.cellWidth
267 height: units.gu(10)
330 height: units.gu(10)
268 objectName: "drawerItem_" + model.appId
331 objectName: "drawerItem_" + model.appId
269
332
333+ property bool focused: index === GridView.view.currentIndex && GridView.view.activeFocus
334+
270 onClicked: root.applicationSelected(model.appId)
335 onClicked: root.applicationSelected(model.appId)
271
336
272 Column {
337 Column {
@@ -290,6 +355,17 @@ FocusScope {
290 source: model.icon
355 source: model.icon
291 }
356 }
292 sourceFillMode: UbuntuShape.PreserveAspectCrop
357 sourceFillMode: UbuntuShape.PreserveAspectCrop
358+
359+ Image {
360+ objectName: "focusRing"
361+ anchors.centerIn: parent
362+ height: width * 15 / 16
363+ width: parent.width + units.gu(1)
364+ source: "graphics/launcher-app-focus-ring.svg"
365+ sourceSize.width: width
366+ sourceSize.height: height
367+ visible: drawerDelegate.focused
368+ }
293 }
369 }
294
370
295 Label {
371 Label {
qml/Launcher/DrawerGridView.qml+3−1
@@ -17,7 +17,7 @@
17import QtQuick 2.4
17import QtQuick 2.4
18import "../Components"
18import "../Components"
19
19
20−Item {
20+FocusScope {
21 id: root
21 id: root
22
22
23 property int delegateWidth: units.gu(10)
23 property int delegateWidth: units.gu(10)
@@ -25,6 +25,7 @@ Item {
25 property alias delegate: gridView.delegate
25 property alias delegate: gridView.delegate
26 property alias model: gridView.model
26 property alias model: gridView.model
27 property alias interactive: gridView.interactive
27 property alias interactive: gridView.interactive
28+ property alias currentIndex: gridView.currentIndex
28
29
29 property alias header: gridView.header
30 property alias header: gridView.header
30 property alias topMargin: gridView.topMargin
31 property alias topMargin: gridView.topMargin
@@ -37,6 +38,7 @@ Item {
37 id: gridView
38 id: gridView
38 anchors.fill: parent
39 anchors.fill: parent
39 leftMargin: spacing
40 leftMargin: spacing
41+ focus: true
40
42
41 readonly property int overflow: width - (root.columns * root.delegateWidth)
43 readonly property int overflow: width - (root.columns * root.delegateWidth)
42 readonly property real spacing: overflow / (root.columns)
44 readonly property real spacing: overflow / (root.columns)
qml/Launcher/DrawerListView.qml+6
@@ -25,6 +25,12 @@ ListView {
25 bottomMargin: units.gu(1)
25 bottomMargin: units.gu(1)
26 spacing: units.gu(1)
26 spacing: units.gu(1)
27 clip: true
27 clip: true
28+ focus: true
29+
30+ onActiveFocusChanged: {
31+ currentIndex = -1;
32+ currentIndex = 0;
33+ }
28
34
29 function getFirstAppId() {
35 function getFirstAppId() {
30 return model.appId(0);
36 return model.appId(0);
qml/Launcher/Launcher.qml+1−2
@@ -539,8 +539,7 @@ FocusScope {
539 if (!dragging) {
539 if (!dragging) {
540 if (distance > panel.width / 2) {
540 if (distance > panel.width / 2) {
541 if (root.drawerEnabled && distance > panel.width * 3 && dragDirection() !== "left") {
541 if (root.drawerEnabled && distance > panel.width * 3 && dragDirection() !== "left") {
542− root.switchToNextState("drawer");
542+ root.openDrawer(false)
543− root.focus = true;
544 } else {
543 } else {
545 root.switchToNextState("visible");
544 root.switchToNextState("visible");
546 }
545 }
qml/Launcher/MoreAppsHeader.qml+2−10
@@ -1,23 +1,15 @@
1import QtQuick 2.4
1import QtQuick 2.4
2import Ubuntu.Components 1.3
2import Ubuntu.Components 1.3
3−import GSettings 1.0
4
3
5AbstractButton {
4AbstractButton {
6 id: root
5 id: root
7
6
8− GSettings {
7+ property bool highlighted: false
9− id: settings
10− schema.id: "com.canonical.Unity8"
11− }
12−
13− onClicked: {
14− Qt.openUrlExternally(settings.appstoreUri)
15− }
16
8
17 UbuntuShape {
9 UbuntuShape {
18 width: parent.width
10 width: parent.width
19 height: parent.height - units.gu(1)
11 height: parent.height - units.gu(1)
20− color: "#20ffffff"
12+ color: root.highlighted ? "red" : "#20ffffff"
21 aspect: UbuntuShape.Flat
13 aspect: UbuntuShape.Flat
22
14
23 Row {
15 Row {