on xenial help sign in
a first cut on implementing the new design requirements in the Launcher
commitbd9f0c16d176d87f5a204ae9d3606b16fc9469fa authorMichael Zanetti <michael.zanetti@canonical.com> dateWed 18 Nov 2015, 14:24 (11 years ago) parent78bdee5
8 files changed+231−32
qml/Components/PhysicalKeysMapper.qml+21 qml/Launcher/Launcher.qml+83−1 qml/Launcher/LauncherDelegate.qml+25−9 qml/Launcher/LauncherPanel.qml+80−18 qml/Shell.qml+6 tests/mocks/Unity/Launcher/MockLauncherModel.cpp+2 tests/qmltests/Launcher/tst_Launcher.qml+2 tests/qmltests/tst_Shell.qml+12−4
qml/Components/PhysicalKeysMapper.qml+21
@@ -41,12 +41,14 @@ Item {
4141 signal screenshotTriggered;
4242
4343 readonly property bool altTabPressed: d.altTabPressed
44+ readonly property bool metaTabPressed: d.metaTabPressed
4445
4546 property int powerKeyLongPressTime: 2000
4647
4748 // For testing. If running windowed (e.g. tryShell), Alt+Tab is taken by the
4849 // running desktop, set this to true to use Ctrl+Tab instead.
4950 property bool controlInsteadOfAlt: false
51+ property bool controlInsteadOfMeta: false
5052
5153 QtObject {
5254 id: d
@@ -58,10 +60,14 @@ Item {
5860 property bool altPressed: false
5961 property bool altTabPressed: false
6062
63+ property bool metaPressed: false
64+ property bool metaTabPressed: false
65+
6166 property var powerButtonPressStart: 0
6267 }
6368
6469 function onKeyPressed(event, currentEventTimestamp) {
70+ print("key pressed", event.key, Qt.Key_Tab)
6571 if (event.key == Qt.Key_PowerDown || event.key == Qt.Key_PowerOff) {
6672 if (event.isAutoRepeat) {
6773 if (d.powerButtonPressStart > 0
@@ -98,11 +104,20 @@ Item {
98104 }
99105 } else if (event.key == Qt.Key_Alt || (root.controlInsteadOfAlt && event.key == Qt.Key_Control)) {
100106 d.altPressed = true;
107+ } else if (event.key == Qt.Key_Meta || (root.controlInsteadOfMeta && event.key == Qt.Key_Control)) {
108+ print("meta pressed")
109+ d.metaPressed = true;
101110 } else if (event.key == Qt.Key_Tab) {
102111 if (d.altPressed && !d.altTabPressed) {
103112 d.altTabPressed = true;
104113 event.accepted = true;
105114 }
115+ print("huh", d.metaPressed)
116+ if (d.metaPressed && !d.metaTabPressed) {
117+ print("metaTab pressed")
118+ d.metaTabPressed = true;
119+ event.accepted = true;
120+ }
106121 }
107122 }
108123
@@ -124,6 +139,12 @@ Item {
124139 d.altTabPressed = false;
125140 event.accepted = true;
126141 }
142+ } else if (event.key == Qt.Key_Meta || (root.controlInsteadOfMeta && event.key == Qt.Key_Control)) {
143+ d.metaPressed = false;
144+ if (d.metaTabPressed) {
145+ d.metaTabPressed = false;
146+ event.accepted = true;
147+ }
127148 }
128149 }
129150}
qml/Launcher/Launcher.qml+83−1
@@ -19,8 +19,9 @@ import "../Components"
1919import Ubuntu.Components 1.3
2020import Ubuntu.Gestures 0.1
2121import Unity.Launcher 0.1
22+import GlobalShortcut 1.0
2223
23−Item {
24+FocusScope {
2425 id: root
2526
2627 property bool autohideEnabled: false
@@ -34,6 +35,8 @@ Item {
3435 property real progress: dragArea.dragging && dragArea.touchX > panelWidth ?
3536 (width * (dragArea.touchX-panelWidth) / (width - panelWidth)) : 0
3637
38+ property bool metaTabPressed: false
39+
3740 readonly property bool dragging: dragArea.dragging
3841 readonly property real dragDistance: dragArea.dragging ? dragArea.touchX : 0
3942 readonly property real visibleWidth: panel.width + panel.x
@@ -57,6 +60,25 @@ Item {
5760 }
5861 }
5962
63+ onMetaTabPressedChanged: {
64+ if (metaTabPressed) {
65+ print("meta tab pressed")
66+ switchToNextState("visible")
67+ panel.highlightIndex = -1;
68+ root.focus = true;
69+ } else {
70+ print("meta tab released")
71+ if (panel.highlightIndex == -1) {
72+ showDashHome();
73+ } else if (panel.highlightIndex >= 0){
74+ launcherApplicationSelected(LauncherModel.get(panel.highlightIndex).appId);
75+ }
76+ panel.highlightIndex = -2;
77+ switchToNextState("");
78+ root.focus = false;
79+ }
80+ }
81+
6082 function hide() {
6183 switchToNextState("")
6284 }
@@ -84,6 +106,66 @@ Item {
84106 }
85107 }
86108
109+ Keys.onPressed: {
110+ print("blubb")
111+ switch (event.key) {
112+ case Qt.Key_Backtab:
113+ panel.highlightPrevious();
114+ event.accepted = true;
115+ break;
116+ case Qt.Key_Up:
117+ if (root.inverted) {
118+ panel.highlightNext()
119+ } else {
120+ panel.highlightPrevious();
121+ }
122+ event.accepted = true;
123+ break;
124+ case Qt.Key_Tab:
125+ panel.highlightNext();
126+ event.accepted = true;
127+ break;
128+ case Qt.Key_Down:
129+ if (root.inverted) {
130+ panel.highlightPrevious();
131+ } else {
132+ panel.highlightNext();
133+ }
134+ event.accepted = true;
135+ break;
136+ case Qt.Key_Right:
137+ panel.openQuicklist(panel.highlightIndex)
138+ event.accepted = true;
139+ break;
140+ case Qt.Key_Escape:
141+ panel.highlightIndex = -2
142+ // Falling through intentionally
143+ case Qt.Key_Enter:
144+ case Qt.Key_Return:
145+ case Qt.Key_Space:
146+ if (panel.highlightIndex == -1) {
147+ showDashHome();
148+ } else if (panel.highlightIndex >= 0) {
149+ launcherApplicationSelected(LauncherModel.get(panel.highlightIndex).appId);
150+ }
151+ root.state = ""
152+ event.accepted = true;
153+ root.focus = false;
154+ }
155+ }
156+
157+ GlobalShortcut {
158+// shortcut: Qt.AltModifier|Qt.Key_F1
159+ shortcut: Qt.Key_F1
160+ onTriggered: {
161+ panel.highlightIndex = -1; // The BFB
162+ root.focus = true;
163+ switchToNextState("visible")
164+ root.focus = true;
165+ print("root has focus", root.focus, root.activeFocus)
166+ }
167+ }
168+
87169 Timer {
88170 id: teaseTimer
89171 interval: mode == "teasing" ? 200 : 300
qml/Launcher/LauncherDelegate.qml+25−9
@@ -29,7 +29,7 @@ Item {
2929 property real maxAngle: 0
3030 property bool inverted: false
3131 property bool alerting: false
32− readonly property alias wiggling: wiggleAnim.running
32+ property bool highlighted: false
3333
3434 readonly property int effectiveHeight: Math.cos(angle * Math.PI / 180) * itemHeight
3535 readonly property real foldedHeight: Math.cos(maxAngle * Math.PI / 180) * itemHeight
@@ -125,6 +125,15 @@ Item {
125125 height: parent.itemHeight + units.gu(1)
126126 anchors.centerIn: parent
127127
128+ UbuntuShape {
129+ anchors.fill: parent
130+ anchors.margins: units.gu(.5)
131+ backgroundColor: "#1986ee"
132+ aspect: UbuntuShape.Flat
133+ radius: "medium"
134+ visible: root.highlighted
135+ }
136+
128137 UbuntuShape {
129138 id: iconShape
130139 anchors.fill: parent
@@ -216,25 +225,32 @@ Item {
216225 }
217226 }
218227
219− Image {
220− objectName: "runningHighlight"
228+ Column {
221229 anchors {
222230 left: parent.left
223231 verticalCenter: parent.verticalCenter
224232 }
225− visible: root.itemRunning
226− rotation: 180
227− source: "graphics/focused_app_arrow.png"
233+ spacing: units.gu(.5)
234+ Repeater {
235+ model: 1
236+ Rectangle {
237+ width: units.gu(0.25)
238+ height: units.gu(.5)
239+ color: "white"
240+ visible: root.itemRunning
241+ }
242+ }
228243 }
229244
230− Image {
231− objectName: "focusedHighlight"
245+ Rectangle {
232246 anchors {
233247 right: parent.right
234248 verticalCenter: parent.verticalCenter
235249 }
250+ width: units.gu(0.25)
251+ height: units.gu(.5)
252+ color: "white"
236253 visible: root.itemFocused
237− source: "graphics/focused_app_arrow.png"
238254 }
239255 }
240256
qml/Launcher/LauncherPanel.qml+80−18
@@ -19,6 +19,7 @@ import Ubuntu.Components 1.3
1919import Ubuntu.Components.ListItems 1.3 as ListItems
2020import Unity.Launcher 0.1
2121import Ubuntu.Components.Popups 1.3
22+import GlobalShortcut 1.0
2223import "../Components/ListItems"
2324import "../Components/"
2425
@@ -33,7 +34,7 @@ Rectangle {
3334 property bool dragging: false
3435 property bool moving: launcherListView.moving || launcherListView.flicking
3536 property bool preventHiding: moving || dndArea.draggedIndex >= 0 || quickList.state === "open" || dndArea.pressed
36− property int highlightIndex: -1
37+ property int highlightIndex: -2
3738
3839 signal applicationSelected(string appId)
3940 signal showDashHome()
@@ -44,6 +45,26 @@ Rectangle {
4445 }
4546 }
4647
48+ function highlightNext() {
49+ highlightIndex++;
50+ if (highlightIndex >= launcherListView.count) {
51+ highlightIndex = -1;
52+ }
53+ moveAnimation.moveToIndex(Math.max(highlightIndex, 0));
54+ }
55+ function highlightPrevious() {
56+ highlightIndex--;
57+ if (highlightIndex <= -2) {
58+ highlightIndex = launcherListView.count - 1;
59+ }
60+ moveAnimation.moveToIndex(Math.max(highlightIndex, 0));
61+ }
62+ function openQuicklist(index) {
63+ quickList.open(index);
64+ quickList.selectedIndex = 0;
65+ quickList.focus = true;
66+ }
67+
4768 Column {
4869 id: mainColumn
4970 anchors {
@@ -172,6 +193,16 @@ Rectangle {
172193 to = contentY;
173194 start();
174195 }
196+ function moveToIndex(index) {
197+ var itemPosition = index * launcherListView.itemHeight;
198+ var height = launcherListView.height - launcherListView.topMargin - launcherListView.bottomMargin
199+ var distanceToEnd = index == 0 || index == launcherListView.count - 1 ? 0 : launcherListView.itemHeight
200+ if (itemPosition + launcherListView.itemHeight + distanceToEnd > launcherListView.contentY + launcherListView.topMargin + height) {
201+ moveAnimation.moveTo(itemPosition + launcherListView.itemHeight - launcherListView.topMargin - height + distanceToEnd);
202+ } else if (itemPosition - distanceToEnd < launcherListView.contentY + launcherListView.topMargin) {
203+ moveAnimation.moveTo(itemPosition - distanceToEnd - launcherListView.topMargin);
204+ }
205+ }
175206 }
176207
177208 displaced: Transition {
@@ -197,6 +228,7 @@ Rectangle {
197228 itemFocused: model.focused
198229 inverted: root.inverted
199230 alerting: model.alerting
231+ highlighted: root.highlightIndex == index
200232 z: -Math.abs(offset)
201233 maxAngle: 55
202234 property bool dragging: false
@@ -234,14 +266,7 @@ Rectangle {
234266 onAlertingChanged: {
235267 if(alerting) {
236268 if (!dragging && (launcherListView.peekingIndex === -1 || launcher.visibleWidth > 0)) {
237− var itemPosition = index * launcherListView.itemHeight;
238− var height = launcherListView.height - launcherListView.topMargin - launcherListView.bottomMargin
239− var distanceToEnd = index == 0 || index == launcherListView.count - 1 ? 0 : launcherListView.itemHeight
240− if (itemPosition + launcherListView.itemHeight + distanceToEnd > launcherListView.contentY + launcherListView.topMargin + height) {
241− moveAnimation.moveTo(itemPosition + launcherListView.itemHeight - launcherListView.topMargin - height + distanceToEnd);
242− } else if (itemPosition - distanceToEnd < launcherListView.contentY + launcherListView.topMargin) {
243− moveAnimation.moveTo(itemPosition - distanceToEnd - launcherListView.topMargin);
244− }
269+ moveAnimation.moveToIndex(index)
245270 if (!dragging && launcher.state !== "visible") {
246271 peekingAnimation.start()
247272 }
@@ -392,10 +417,7 @@ Rectangle {
392417
393418 if (mouse.button & Qt.RightButton) { // context menu
394419 // Opening QuickList
395− quickList.item = clickedItem;
396− quickList.model = launcherListView.model.get(index).quickList;
397− quickList.appId = launcherListView.model.get(index).appId;
398− quickList.state = "open";
420+ quickList.open(index);
399421 return
400422 }
401423
@@ -465,11 +487,7 @@ Rectangle {
465487
466488 draggedIndex = Math.floor((mouseY + launcherListView.realContentY) / launcherListView.realItemHeight);
467489
468− // Opening QuickList
469− quickList.item = selectedItem;
470− quickList.model = launcherListView.model.get(draggedIndex).quickList;
471− quickList.appId = launcherListView.model.get(draggedIndex).appId;
472− quickList.state = "open";
490+ quickList.open(draggedIndex)
473491
474492 launcherListView.interactive = false
475493
@@ -647,12 +665,55 @@ Rectangle {
647665 property var model
648666 property string appId
649667 property var item
668+ property int selectedIndex: -1
669+
670+ Keys.onPressed: {
671+ switch (event.key) {
672+ case Qt.Key_Down:
673+ selectedIndex++;
674+ if (selectedIndex >= popoverRepeater.count) {
675+ selectedIndex = 0;
676+ }
677+ event.accepted = true;
678+ break;
679+ case Qt.Key_Up:
680+ selectedIndex--;
681+ if (selectedIndex < 0) {
682+ selectedIndex = popoverRepeater.count - 1;
683+ }
684+ event.accepted = true;
685+ break;
686+ case Qt.Key_Left:
687+ case Qt.Key_Escape:
688+ quickList.selectedIndex = -1;
689+ // Falling through intentionally
690+ case Qt.Key_Enter:
691+ case Qt.Key_Return:
692+ case Qt.Key_Space:
693+ if (quickList.selectedIndex >= 0) {
694+ LauncherModel.quickListActionInvoked(quickList.appId, quickList.selectedIndex)
695+ }
696+ quickList.focus = false;
697+ quickList.state = ""
698+ // Don't consume the event. We want to close the Launcher too, not just the quicklist.
699+ break;
700+ }
701+ }
650702
651703 // internal
652704 property int itemCenter: item ? root.mapFromItem(quickList.item).y + (item.height / 2) : units.gu(1)
653705 property int offset: itemCenter + (height/2) + units.gu(1) > parent.height ? -itemCenter - (height/2) - units.gu(1) + parent.height :
654706 itemCenter - (height/2) < units.gu(1) ? (height/2) - itemCenter + units.gu(1) : 0
655707
708+ function open(index) {
709+ var itemPosition = index * launcherListView.itemHeight;
710+ var height = launcherListView.height - launcherListView.topMargin - launcherListView.bottomMargin
711+ item = launcherListView.itemAt(launcherListView.width / 2, itemPosition + launcherListView.itemHeight / 2);
712+ quickList.model = launcherListView.model.get(index).quickList;
713+ quickList.appId = launcherListView.model.get(index).appId;
714+ quickList.state = "open";
715+ }
716+
656717 Column {
657718 id: quickListColumn
658719 width: parent.width
@@ -666,6 +727,7 @@ Rectangle {
666727 objectName: "quickListEntry" + index
667728 text: (model.clickable ? "" : "<b>") + model.label + (model.clickable ? "" : "</b>")
668729 highlightWhenPressed: model.clickable
730+ selected: index === quickList.selectedIndex
669731
670732 // FIXME: This is a workaround for the theme not being context sensitive. I.e. the
671733 // ListItems don't know that they are sitting in a themed Popover where the color
qml/Shell.qml+6
@@ -550,6 +550,7 @@ Item {
550550 && !greeter.hasLockedApp
551551 inverted: shell.usageScenario !== "desktop"
552552 shadeBackground: !tutorial.running
553+ metaTabPressed: physicalKeysMapper.metaTabPressed
553554
554555 onShowDashHome: showHome()
555556 onDash: showDash()
@@ -569,6 +570,11 @@ Item {
569570 panel.indicators.hide()
570571 }
571572 }
573+ onFocusChanged: {
574+ if (!focus) {
575+ applicationsDisplayLoader.focus = true;
576+ }
577+ }
572578 }
573579
574580 Wizard {
tests/mocks/Unity/Launcher/MockLauncherModel.cpp+2
@@ -25,6 +25,7 @@ MockLauncherModel::MockLauncherModel(QObject* parent): LauncherModelInterface(pa
2525 MockLauncherItem *item = new MockLauncherItem("dialer-app", "/usr/share/applications/dialer-app.desktop", "Dialer", "dialer-app", this);
2626 item->setProgress(0);
2727 item->setPinned(true);
28+ item->setRunning(true);
2829 item->setFocused(true);
2930 m_list.append(item);
3031 item = new MockLauncherItem("camera-app", "/usr/share/applications/camera-app.desktop", "Camera", "camera", this);
@@ -34,6 +35,7 @@ MockLauncherModel::MockLauncherModel(QObject* parent): LauncherModelInterface(pa
3435 item = new MockLauncherItem("gallery-app", "/usr/share/applications/gallery-app.desktop", "Gallery", "gallery", this);
3536 item->setProgress(50);
3637 item->setCountVisible(true);
38+ item->setRunning(true);
3739 item->setAlerting(false);
3840 m_list.append(item);
3941 item = new MockLauncherItem("music-app", "/usr/share/applications/music-app.desktop", "Music", "soundcloud", this);
tests/qmltests/Launcher/tst_Launcher.qml+2
@@ -30,9 +30,11 @@ Item {
3030 width: units.gu(50)
3131 height: units.gu(55)
3232
33+ Keys.onPressed: print("waaah")
3334 Loader {
3435 id: launcherLoader
3536 anchors.fill: parent
37+ focus: true
3638 property bool itemDestroyed: false
3739 sourceComponent: Component {
3840 Launcher {
tests/qmltests/tst_Shell.qml+12−4
@@ -116,10 +116,6 @@ Rectangle {
116116 Component.onDestruction: {
117117 shellLoader.itemDestroyed = true;
118118 }
119− Component.onCompleted: {
120− var keyMapper = testCase.findChild(__shell, "physicalKeysMapper");
121− keyMapper.controlInsteadOfAlt = true;
122− }
123119 }
124120 }
125121 }
@@ -208,6 +204,18 @@ Rectangle {
208204 checked: true
209205 color: "white"
210206 }
207+ ListItem.ItemSelector {
208+ id: ctrlModifier
209+ anchors { left: parent.left; right: parent.right }
210+ activeFocusOnPress: false
211+ text: "Ctrl key as"
212+ model: ["Ctrl", "Alt", "Meta"]
213+ onSelectedIndexChanged: {
214+ var keyMapper = testCase.findChild(shellContainer, "physicalKeysMapper");
215+ keyMapper.controlInsteadOfAlt = selectedIndex == 1;
216+ keyMapper.controlInsteadOfMeta = selectedIndex == 2;
217+ }
218+ }
211219
212220 Label { text: "Applications"; font.bold: true }
213221