on xenial help sign in
Make launcher scalable, allow it locking Fixes: #1511015

Approved by: Unity8 CI Bot, Lukáš Tinkl

commit2584355363eae508f49592fdcfed3491c0241877 authorMichael Zanetti <michael.zanetti@canonical.com> dateThu 10 Mar 2016, 22:37 (11 years ago) parent82e4b97391bd94
12 files changed+407−109
data/com.canonical.Unity8.gschema.xml+11 qml/Launcher/Launcher.qml+32−10 qml/Launcher/LauncherDelegate.qml+8−12 qml/Launcher/LauncherPanel.qml+34−34 qml/Shell.qml+18−1 qml/Stages/AbstractStage.qml+2 qml/Stages/DesktopStage.qml+15−12 qml/Stages/WindowResizeArea.qml+3−2 tests/mocks/GSettings.1.0/fake_gsettings.cpp+64 tests/mocks/GSettings.1.0/fake_gsettings.h+18 tests/qmltests/Launcher/tst_Launcher.qml+110−25 tests/qmltests/tst_Shell.qml+92−13
data/com.canonical.Unity8.gschema.xml+11
@@ -27,6 +27,17 @@
2727 <summary>Maximum push needed to overcome edge barrier</summary>
2828 <description>How much you have to push (in grid units) the mouse against an edge barrier when sensibility is 1.</description>
2929 </key>
30+ <key type="b" name="autohide-launcher">
31+ <default>true</default>
32+ <summary>Autohide the launcher</summary>
33+ <description>This will only be applied in windowed mode. In staged mode, the launcher will always hide.</description>
34+ </key>
35+ <key type="u" name="launcher-width">
36+ <default>8</default>
37+ <range min="6" max="12"/>
38+ <summary>Width of the launcher in grid units.</summary>
39+ <description>Changes the width of the launcher in all usage modes.</description>
40+ </key>
3041 </schema>
3142
3243 <schema path="/com/canonical/unity8/greeter/" id="com.canonical.Unity8.Greeter" gettext-domain="unity8">
qml/Launcher/Launcher.qml+32−10
@@ -25,11 +25,12 @@ FocusScope {
2525 id: root
2626
2727 property bool autohideEnabled: false
28+ property bool lockedVisible: false
2829 property bool available: true // can be used to disable all interactions
2930 property alias inverted: panel.inverted
3031 property bool shadeBackground: true // can be used to disable background shade when launcher is visible
3132
32− property int panelWidth: units.gu(8)
33+ property int panelWidth: units.gu(10)
3334 property int dragAreaWidth: units.gu(1)
3435 property int minimizeDistance: units.gu(26)
3536 property real progress: dragArea.dragging && dragArea.touchX > panelWidth ?
@@ -92,12 +93,24 @@ FocusScope {
9293 }
9394 }
9495
96+ onLockedVisibleChanged: {
97+ if (lockedVisible && state == "") {
98+ panel.dismissTimer.stop();
99+ fadeOutAnimation.stop();
100+ switchToNextState("visible")
101+ } else if (!lockedVisible && state == "visible") {
102+ hide();
103+ }
104+ }
105+
95106 function hide() {
96107 switchToNextState("")
97108 }
98109
99110 function fadeOut() {
100− fadeOutAnimation.start();
111+ if (!root.lockedVisible) {
112+ fadeOutAnimation.start();
113+ }
101114 }
102115
103116 function switchToNextState(state) {
@@ -172,8 +185,8 @@ FocusScope {
172185 } else if (panel.highlightIndex >= 0) {
173186 launcherApplicationSelected(LauncherModel.get(panel.highlightIndex).appId);
174187 }
188+ root.hide();
175189 panel.highlightIndex = -2
176− root.state = ""
177190 event.accepted = true;
178191 root.focus = false;
179192 }
@@ -212,6 +225,13 @@ FocusScope {
212225 interval: 1
213226 property string nextState: ""
214227 onTriggered: {
228+ if (root.lockedVisible && nextState == "") {
229+ // Due to binding updates when switching between modes
230+ // it could happen that our request to show will be overwritten
231+ // with a hide request. Rewrite it when we know hiding is not allowed.
232+ nextState = "visible"
233+ }
234+
215235 // switching to an intermediate state here to make sure all the
216236 // values are restored, even if we were already in the target state
217237 root.state = "tmp"
@@ -257,7 +277,7 @@ FocusScope {
257277
258278 MouseArea {
259279 id: launcherDragArea
260− enabled: root.available && (root.state == "visible" || root.state == "visibleTemporary")
280+ enabled: root.available && (root.state == "visible" || root.state == "visibleTemporary") && !root.lockedVisible
261281 anchors.fill: panel
262282 anchors.rightMargin: -units.gu(2)
263283 drag {
@@ -278,9 +298,10 @@ FocusScope {
278298 InverseMouseArea {
279299 id: closeMouseArea
280300 anchors.fill: panel
281− enabled: root.shadeBackground && root.state == "visible"
301+ enabled: root.shadeBackground && root.state == "visible" && (!root.lockedVisible || panel.highlightIndex >= -1)
282302 visible: enabled
283303 onPressed: {
304+ panel.highlightIndex = -2
284305 root.hide();
285306 }
286307 }
@@ -289,7 +310,7 @@ FocusScope {
289310 id: backgroundShade
290311 anchors.fill: parent
291312 color: "black"
292− opacity: root.shadeBackground && root.state == "visible" ? 0.6 : 0
313+ opacity: root.shadeBackground && root.state == "visible" && !root.lockedVisible ? 0.6 : 0
293314
294315 Behavior on opacity { NumberAnimation { duration: UbuntuAnimation.BriskDuration } }
295316 }
@@ -333,7 +354,7 @@ FocusScope {
333354 Connections {
334355 target: panel.dismissTimer
335356 onTriggered: {
336− if (root.autohideEnabled) {
357+ if (root.autohideEnabled && !root.lockedVisible) {
337358 if (!panel.preventHiding) {
338359 root.state = ""
339360 } else {
@@ -346,11 +367,11 @@ FocusScope {
346367 property bool animate: true
347368
348369 onApplicationSelected: {
349− root.state = ""
370+ root.hide();
350371 launcherApplicationSelected(appId)
351372 }
352373 onShowDashHome: {
353− root.state = ""
374+ root.hide();
354375 root.showDashHome();
355376 }
356377
@@ -361,7 +382,8 @@ FocusScope {
361382 }
362383
363384 onKbdNavigationCancelled: {
364− root.state = "";
385+ panel.highlightIndex = -2;
386+ root.hide();
365387 root.focus = false;
366388 }
367389
qml/Launcher/LauncherDelegate.qml+8−12
@@ -124,7 +124,7 @@ Item {
124124
125125 Item {
126126 id: iconItem
127− width: parent.itemWidth + units.gu(1)
127+ width: root.width
128128 height: parent.itemHeight + units.gu(1)
129129 anchors.centerIn: parent
130130
@@ -142,7 +142,7 @@ Item {
142142 ProportionalShape {
143143 id: iconShape
144144 anchors.centerIn: parent
145− width: parent.width - units.gu(2)
145+ width: root.itemWidth
146146 aspect: UbuntuShape.DropShadow
147147 source: Image {
148148 id: iconImage
@@ -158,7 +158,8 @@ Item {
158158 anchors {
159159 right: parent.right
160160 bottom: parent.bottom
161− margins: units.dp(3)
161+ rightMargin: (iconItem.width - root.itemWidth) / 2 - units.dp(2)
162+ margins: units.dp(5)
162163 }
163164 width: Math.min(root.itemWidth, Math.max(units.gu(2), countLabel.implicitWidth + units.gu(1)))
164165 height: units.gu(2)
@@ -186,16 +187,11 @@ Item {
186187 id: progressOverlay
187188 objectName: "progressOverlay"
188189
189− anchors {
190− left: iconItem.left
191− right: iconItem.right
192− verticalCenter: parent.verticalCenter
193− leftMargin: units.gu(1.5)
194− rightMargin: units.gu(1.5)
195− }
190+ anchors.centerIn: parent
191+ width: root.itemWidth * .8
196192 height: units.gu(1)
197193 visible: root.progress > -1
198− color: UbuntuColors.darkGrey
194+ backgroundColor: UbuntuColors.darkGrey
199195 borderSource: "none"
200196
201197 Item {
@@ -213,7 +209,7 @@ Item {
213209 top: parent.top
214210 bottom: parent.bottom
215211 }
216− color: "white"
212+ backgroundColor: "white"
217213 borderSource: "none"
218214 width: progressOverlay.width
219215 }
qml/Launcher/LauncherPanel.qml+34−34
@@ -53,14 +53,14 @@ Rectangle {
5353 if (highlightIndex >= launcherListView.count) {
5454 highlightIndex = -1;
5555 }
56− moveAnimation.moveToIndex(Math.max(highlightIndex, 0));
56+ launcherListView.moveToIndex(Math.max(highlightIndex, 0));
5757 }
5858 function highlightPrevious() {
5959 highlightIndex--;
6060 if (highlightIndex <= -2) {
6161 highlightIndex = launcherListView.count - 1;
6262 }
63− moveAnimation.moveToIndex(Math.max(highlightIndex, 0));
63+ launcherListView.moveToIndex(Math.max(highlightIndex, 0));
6464 }
6565 function openQuicklist(index) {
6666 quickList.open(index);
@@ -83,13 +83,13 @@ Rectangle {
8383 Rectangle {
8484 objectName: "buttonShowDashHome"
8585 width: parent.width
86− height: units.gu(7)
86+ height: width * .9
8787 color: UbuntuColors.orange
8888 readonly property bool highlighted: root.highlightIndex == -1;
8989
9090 Image {
9191 objectName: "dashItem"
92− width: units.gu(5)
92+ width: parent.width * .6
9393 height: width
9494 anchors.centerIn: parent
9595 source: "graphics/home.png"
@@ -125,10 +125,8 @@ Rectangle {
125125 objectName: "launcherListView"
126126 anchors {
127127 fill: parent
128− topMargin: -extensionSize + units.gu(0.5)
129− bottomMargin: -extensionSize + units.gu(1)
130− leftMargin: units.gu(0.5)
131− rightMargin: units.gu(0.5)
128+ topMargin: -extensionSize + width * .15
129+ bottomMargin: -extensionSize + width * .15
132130 }
133131 topMargin: extensionSize
134132 bottomMargin: extensionSize
@@ -163,11 +161,11 @@ Rectangle {
163161 }
164162
165163 // The height of the area where icons start getting folded
166− property int foldingStartHeight: units.gu(6.5)
164+ property int foldingStartHeight: itemHeight
167165 // The height of the area where the items reach the final folding angle
168166 property int foldingStopHeight: foldingStartHeight - itemHeight - spacing
169− property int itemWidth: units.gu(7)
170− property int itemHeight: units.gu(6.5)
167+ property int itemWidth: width * .75
168+ property int itemHeight: itemWidth * 15 / 16 + units.gu(1)
171169 property int clickFlickSpeed: units.gu(60)
172170 property int draggedIndex: dndArea.draggedIndex
173171 property real realContentY: contentY - originY + topMargin
@@ -195,22 +193,24 @@ Rectangle {
195193
196194 UbuntuNumberAnimation {
197195 id: moveAnimation
196+ objectName: "moveAnimation"
198197 target: launcherListView
199198 property: "contentY"
200199 function moveTo(contentY) {
201200 from = launcherListView.contentY;
202201 to = contentY;
203− start();
202+ restart();
204203 }
205− function moveToIndex(index) {
206− var itemPosition = index * launcherListView.itemHeight;
207− var height = launcherListView.height - launcherListView.topMargin - launcherListView.bottomMargin
208− var distanceToEnd = index == 0 || index == launcherListView.count - 1 ? 0 : launcherListView.itemHeight
209− if (itemPosition + launcherListView.itemHeight + distanceToEnd > launcherListView.contentY + launcherListView.topMargin + height) {
210− moveAnimation.moveTo(itemPosition + launcherListView.itemHeight - launcherListView.topMargin - height + distanceToEnd);
211− } else if (itemPosition - distanceToEnd < launcherListView.contentY + launcherListView.topMargin) {
212− moveAnimation.moveTo(itemPosition - distanceToEnd - launcherListView.topMargin);
213− }
204+ }
205+ function moveToIndex(index) {
206+ var totalItemHeight = launcherListView.itemHeight + launcherListView.spacing
207+ var itemPosition = index * totalItemHeight;
208+ var height = launcherListView.height - launcherListView.topMargin - launcherListView.bottomMargin
209+ var distanceToEnd = index == 0 || index == launcherListView.count - 1 ? 0 : totalItemHeight
210+ if (itemPosition + totalItemHeight + distanceToEnd > launcherListView.contentY + launcherListView.originY + launcherListView.topMargin + height) {
211+ moveAnimation.moveTo(itemPosition + launcherListView.itemHeight - launcherListView.topMargin - height + distanceToEnd - launcherListView.originY);
212+ } else if (itemPosition - distanceToEnd < launcherListView.contentY - launcherListView.originY + launcherListView.topMargin) {
213+ moveAnimation.moveTo(itemPosition - distanceToEnd - launcherListView.topMargin + launcherListView.originY);
214214 }
215215 }
216216
@@ -228,7 +228,7 @@ Rectangle {
228228 itemIndex: index
229229 itemHeight: launcherListView.itemHeight
230230 itemWidth: launcherListView.itemWidth
231− width: itemWidth
231+ width: parent.width
232232 height: itemHeight
233233 iconName: model.icon
234234 count: model.count
@@ -277,7 +277,7 @@ Rectangle {
277277 onAlertingChanged: {
278278 if(alerting) {
279279 if (!dragging && (launcherListView.peekingIndex === -1 || launcher.visibleWidth > 0)) {
280− moveAnimation.moveToIndex(index)
280+ launcherListView.moveToIndex(index)
281281 if (!dragging && launcher.state !== "visible") {
282282 peekingAnimation.start()
283283 }
@@ -439,10 +439,8 @@ Rectangle {
439439
440440 // First/last item do the scrolling at more than 12 degrees
441441 if (index == 0 || index == launcherListView.count - 1) {
442− if (clickedItem.angle > 12) {
443− launcherListView.flick(0, -launcherListView.clickFlickSpeed);
444− } else if (clickedItem.angle < -12) {
445− launcherListView.flick(0, launcherListView.clickFlickSpeed);
442+ if (clickedItem.angle > 12 || clickedItem.angle < -12) {
443+ launcherListView.moveToIndex(index);
446444 } else {
447445 root.applicationSelected(LauncherModel.get(index).appId);
448446 }
@@ -450,10 +448,8 @@ Rectangle {
450448 }
451449
452450 // the rest launches apps up to an angle of 30 degrees
453− if (clickedItem.angle > 30) {
454− launcherListView.flick(0, -launcherListView.clickFlickSpeed);
455− } else if (clickedItem.angle < -30) {
456− launcherListView.flick(0, launcherListView.clickFlickSpeed);
451+ if (clickedItem.angle > 30 || clickedItem.angle < -30) {
452+ launcherListView.moveToIndex(index);
457453 } else {
458454 root.applicationSelected(LauncherModel.get(index).appId);
459455 }
@@ -668,7 +664,6 @@ Rectangle {
668664 onClicked: {
669665 quickList.state = "";
670666 quickList.focus = false;
671− root.highlightIndex = -2;
672667 root.kbdNavigationCancelled();
673668 }
674669
@@ -739,16 +734,21 @@ Rectangle {
739734 case Qt.Key_Left:
740735 case Qt.Key_Escape:
741736 quickList.selectedIndex = -1;
742− // Falling through intentionally
737+ quickList.focus = false;
738+ quickList.state = ""
739+ event.accepted = true;
740+ break;
743741 case Qt.Key_Enter:
744742 case Qt.Key_Return:
745743 case Qt.Key_Space:
746744 if (quickList.selectedIndex >= 0) {
747745 LauncherModel.quickListActionInvoked(quickList.appId, quickList.selectedIndex)
748746 }
747+ quickList.selectedIndex = -1;
749748 quickList.focus = false;
750749 quickList.state = ""
751− // Don't consume the event. We want to close the Launcher too, not just the quicklist.
750+ root.kbdNavigationCancelled();
751+ event.accepted = true;
752752 break;
753753 }
754754 }
qml/Shell.qml+18−1
@@ -25,6 +25,7 @@ import Ubuntu.Telephony 0.1 as Telephony
2525import Unity.Connectivity 0.1
2626import Unity.Launcher 0.1
2727import GlobalShortcut 1.0 // has to be before Utils, because of WindowKeysFilter
28+import GSettings 1.0
2829import Utils 0.1
2930import Powerd 0.1
3031import SessionBroadcast 0.1
@@ -187,6 +188,11 @@ Item {
187188 }
188189 }
189190
191+ GSettings {
192+ id: settings
193+ schema.id: "com.canonical.Unity8"
194+ }
195+
190196 Item {
191197 id: stages
192198 objectName: "stages"
@@ -343,6 +349,11 @@ Item {
343349 property: "altTabPressed"
344350 value: physicalKeysMapper.altTabPressed
345351 }
352+ Binding {
353+ target: applicationsDisplayLoader.item
354+ property: "leftMargin"
355+ value: shell.usageScenario == "desktop" && !settings.autohideLauncher ? launcher.panelWidth: 0
356+ }
346357 }
347358
348359 Tutorial {
@@ -373,7 +384,11 @@ Item {
373384 InputMethod {
374385 id: inputMethod
375386 objectName: "inputMethod"
376− anchors { fill: parent; topMargin: panel.panelHeight }
387+ anchors {
388+ fill: parent
389+ topMargin: panel.panelHeight
390+ leftMargin: launcher.lockedVisible ? launcher.panelWidth : 0
391+ }
377392 z: notifications.useModal || panel.indicators.shown || wizard.active ? overlay.z + 1 : overlay.z - 1
378393 }
379394
@@ -559,6 +574,8 @@ Item {
559574 shadeBackground: !tutorial.running
560575 superPressed: physicalKeysMapper.superPressed
561576 superTabPressed: physicalKeysMapper.superTabPressed
577+ panelWidth: units.gu(settings.launcherWidth)
578+ lockedVisible: shell.usageScenario == "desktop" && !settings.autohideLauncher && !panel.fullscreenMode
562579
563580 onShowDashHome: showHome()
564581 onDash: showDash()
qml/Stages/AbstractStage.qml+2
@@ -39,6 +39,8 @@ Rectangle {
3939 property int shellOrientationAngle
4040 property bool spreadEnabled: true // If false, animations and right edge will be disabled
4141 property bool suspended
42+ // A Stage should paint a wallpaper etc over its full size but not use the margins for window placement
43+ property int leftMargin: 0
4244
4345 // To be read from outside
4446 property var mainApp: null
qml/Stages/DesktopStage.qml+15−12
@@ -224,6 +224,7 @@ AbstractStage {
224224 PanelState.dropShadow = false;
225225 }
226226
227+
227228 FocusScope {
228229 id: appContainer
229230 objectName: "appContainer"
@@ -293,6 +294,7 @@ AbstractStage {
293294 visible: !visuallyMinimized &&
294295 !greeter.fullyShown &&
295296 (priv.foregroundMaximizedAppZ === -1 || priv.foregroundMaximizedAppZ <= z) ||
297+ decoratedWindow.fullscreen ||
296298 (spread.state == "altTab" && index === spread.highlightedIndex)
297299
298300 Binding {
@@ -351,10 +353,10 @@ AbstractStage {
351353 states: [
352354 State {
353355 name: "fullscreen"; when: decoratedWindow.fullscreen
354− extend: "maximized"
355356 PropertyChanges {
356357 target: appDelegate;
357− y: -PanelState.panelHeight
358+ x: 0; y: -PanelState.panelHeight
359+ requestedWidth: appContainer.width; requestedHeight: appContainer.height;
358360 }
359361 },
360362 State {
@@ -371,21 +373,21 @@ AbstractStage {
371373 name: "maximized"; when: appDelegate.maximized && !appDelegate.minimized
372374 PropertyChanges {
373375 target: appDelegate;
374− x: 0; y: 0;
375− requestedWidth: root.width; requestedHeight: root.height;
376+ x: root.leftMargin; y: 0;
377+ requestedWidth: appContainer.width - root.leftMargin; requestedHeight: appContainer.height;
376378 visuallyMinimized: false;
377379 visuallyMaximized: true
378380 }
379381 },
380382 State {
381383 name: "maximizedLeft"; when: appDelegate.maximizedLeft && !appDelegate.minimized
382− PropertyChanges { target: appDelegate; x: 0; y: PanelState.panelHeight;
383− requestedWidth: root.width/2; requestedHeight: root.height - PanelState.panelHeight }
384+ PropertyChanges { target: appDelegate; x: root.leftMargin; y: PanelState.panelHeight;
385+ requestedWidth: (appContainer.width - root.leftMargin)/2; requestedHeight: appContainer.height - PanelState.panelHeight }
384386 },
385387 State {
386388 name: "maximizedRight"; when: appDelegate.maximizedRight && !appDelegate.minimized
387− PropertyChanges { target: appDelegate; x: root.width/2; y: PanelState.panelHeight;
388− requestedWidth: root.width/2; requestedHeight: root.height - PanelState.panelHeight }
389+ PropertyChanges { target: appDelegate; x: (appContainer.width + root.leftMargin)/2; y: PanelState.panelHeight;
390+ requestedWidth: (appContainer.width - root.leftMargin)/2; requestedHeight: appContainer.height - PanelState.panelHeight }
389391 },
390392 State {
391393 name: "minimized"; when: appDelegate.minimized
@@ -448,8 +450,9 @@ AbstractStage {
448450 minHeight: units.gu(10)
449451 borderThickness: units.gu(2)
450452 windowId: model.appId // FIXME: Change this to point to windowId once we have such a thing
451− screenWidth: root.width
452− screenHeight: root.height
453+ screenWidth: appContainer.width
454+ screenHeight: appContainer.height
455+ leftMargin: root.leftMargin
453456
454457 onPressed: { ApplicationManager.focusApplication(model.appId) }
455458 }
@@ -475,7 +478,7 @@ AbstractStage {
475478
476479 BlurLayer {
477480 id: blurLayer
478− anchors.fill: parent
481+ anchors.fill: appContainer
479482 source: appContainer
480483 visible: false
481484 }
@@ -527,7 +530,7 @@ AbstractStage {
527530 DesktopSpread {
528531 id: spread
529532 objectName: "spread"
530− anchors.fill: parent
533+ anchors.fill: appContainer
531534 workspace: appContainer
532535 focus: state == "altTab"
533536 altTabPressed: root.altTabPressed
qml/Stages/WindowResizeArea.qml+3−2
@@ -40,6 +40,7 @@ MouseArea {
4040 property int defaultHeight: units.gu(50)
4141 property int screenWidth: 0
4242 property int screenHeight: 0
43+ property int leftMargin: 0
4344
4445 QtObject {
4546 id: priv
@@ -72,9 +73,9 @@ MouseArea {
7273 var windowGeometry = windowStateStorage.getGeometry(root.windowId,
7374 Qt.rect(target.x, target.y, defaultWidth, defaultHeight));
7475
75− target.requestedWidth = Math.min(Math.max(windowGeometry.width, d.minimumWidth), screenWidth);
76+ target.requestedWidth = Math.min(Math.max(windowGeometry.width, d.minimumWidth), screenWidth - root.leftMargin);
7677 target.requestedHeight = Math.min(Math.max(windowGeometry.height, d.minimumHeight), root.screenHeight - PanelState.panelHeight);
77− target.x = Math.max(Math.min(windowGeometry.x, root.screenWidth - target.requestedWidth), 0)
78+ target.x = Math.max(Math.min(windowGeometry.x, root.screenWidth - root.leftMargin - target.requestedWidth), root.leftMargin)
7879 target.y = Math.max(Math.min(windowGeometry.y, root.screenHeight - target.requestedHeight), PanelState.panelHeight)
7980
8081 var windowState = windowStateStorage.getState(root.windowId, WindowStateStorage.WindowStateNormal)
tests/mocks/GSettings.1.0/fake_gsettings.cpp+64
@@ -22,6 +22,8 @@ GSettingsControllerQml* GSettingsControllerQml::s_controllerInstance = 0;
2222
2323GSettingsControllerQml::GSettingsControllerQml()
2424 : m_usageMode("Staged")
25+ , m_autohideLauncher(false)
26+ , m_launcherWidth(8)
2527{
2628}
2729
@@ -88,6 +90,32 @@ void GSettingsControllerQml::setLifecycleExemptAppids(const QStringList &appIds)
8890 }
8991}
9092
93+bool GSettingsControllerQml::autohideLauncher() const
94+{
95+ return m_autohideLauncher;
96+}
97+
98+void GSettingsControllerQml::setAutohideLauncher(bool autohideLauncher)
99+{
100+ if (m_autohideLauncher != autohideLauncher) {
101+ m_autohideLauncher = autohideLauncher;
102+ Q_EMIT autohideLauncherChanged(autohideLauncher);
103+ }
104+}
105+
106+int GSettingsControllerQml::launcherWidth() const
107+{
108+ return m_launcherWidth;
109+}
110+
111+void GSettingsControllerQml::setLauncherWidth(int launcherWidth)
112+{
113+ if (m_launcherWidth != launcherWidth) {
114+ m_launcherWidth = launcherWidth;
115+ Q_EMIT launcherWidthChanged(launcherWidth);
116+ }
117+}
118+
91119GSettingsSchemaQml::GSettingsSchemaQml(QObject *parent): QObject(parent) {
92120}
93121
@@ -129,6 +157,10 @@ GSettingsQml::GSettingsQml(QObject *parent)
129157 this, &GSettingsQml::lockedOutTimeChanged);
130158 connect(GSettingsControllerQml::instance(), &GSettingsControllerQml::lifecycleExemptAppidsChanged,
131159 this, &GSettingsQml::lifecycleExemptAppidsChanged);
160+ connect(GSettingsControllerQml::instance(), &GSettingsControllerQml::autohideLauncherChanged,
161+ this, &GSettingsQml::autohideLauncherChanged);
162+ connect(GSettingsControllerQml::instance(), &GSettingsControllerQml::launcherWidthChanged,
163+ this, &GSettingsQml::launcherWidthChanged);
132164}
133165
134166GSettingsSchemaQml * GSettingsQml::schema() const {
@@ -192,9 +224,41 @@ QStringList GSettingsQml::lifecycleExemptAppids() const
192224 }
193225}
194226
227+bool GSettingsQml::autohideLauncher() const
228+{
229+ if (m_schema->id() == "com.canonical.Unity8") {
230+ return GSettingsControllerQml::instance()->autohideLauncher();
231+ } else {
232+ return false;
233+ }
234+}
235+
236+int GSettingsQml::launcherWidth() const
237+{
238+ if (m_schema->id() == "com.canonical.Unity8") {
239+ return GSettingsControllerQml::instance()->launcherWidth();
240+ } else {
241+ return false;
242+ }
243+}
244+
195245void GSettingsQml::setLifecycleExemptAppids(const QStringList &appIds)
196246{
197247 if (m_schema->id() == "com.canonical.qtmir") {
198248 GSettingsControllerQml::instance()->setLifecycleExemptAppids(appIds);
199249 }
200250}
251+
252+void GSettingsQml::setAutohideLauncher(bool autohideLauncher)
253+{
254+ if (m_schema->id() == "com.canonical.Unity8") {
255+ GSettingsControllerQml::instance()->setAutohideLauncher(autohideLauncher);
256+ }
257+}
258+
259+void GSettingsQml::setLauncherWidth(int launcherWidth)
260+{
261+ if (m_schema->id() == "com.canonical.Unity8") {
262+ GSettingsControllerQml::instance()->setLauncherWidth(launcherWidth);
263+ }
264+}
tests/mocks/GSettings.1.0/fake_gsettings.h+18
@@ -50,6 +50,8 @@ class GSettingsQml: public QObject
5050 Q_PROPERTY(QString usageMode READ usageMode WRITE setUsageMode NOTIFY usageModeChanged)
5151 Q_PROPERTY(qint64 lockedOutTime READ lockedOutTime WRITE setLockedOutTime NOTIFY lockedOutTimeChanged)
5252 Q_PROPERTY(QStringList lifecycleExemptAppids READ lifecycleExemptAppids WRITE setLifecycleExemptAppids NOTIFY lifecycleExemptAppidsChanged)
53+ Q_PROPERTY(bool autohideLauncher READ autohideLauncher WRITE setAutohideLauncher NOTIFY autohideLauncherChanged)
54+ Q_PROPERTY(int launcherWidth READ launcherWidth WRITE setLauncherWidth NOTIFY launcherWidthChanged)
5355
5456public:
5557 GSettingsQml(QObject *parent = nullptr);
@@ -59,11 +61,15 @@ public:
5961 QString usageMode() const;
6062 qint64 lockedOutTime() const;
6163 QStringList lifecycleExemptAppids() const;
64+ bool autohideLauncher() const;
65+ int launcherWidth() const;
6266
6367 void setPictureUri(const QString &str);
6468 void setUsageMode(const QString &usageMode);
6569 void setLockedOutTime(qint64 timestamp);
6670 void setLifecycleExemptAppids(const QStringList &appIds);
71+ void setAutohideLauncher(bool autohideLauncher);
72+ void setLauncherWidth(int launcherWidth);
6773
6874Q_SIGNALS:
6975 void schemaChanged();
@@ -71,6 +77,8 @@ Q_SIGNALS:
7177 void usageModeChanged(const QString&);
7278 void lockedOutTimeChanged(qint64);
7379 void lifecycleExemptAppidsChanged(const QStringList &);
80+ void autohideLauncherChanged(bool);
81+ void launcherWidthChanged(int launcherWidth);
7482
7583private:
7684 GSettingsSchemaQml* m_schema;
@@ -98,11 +106,19 @@ public:
98106 QStringList lifecycleExemptAppids() const;
99107 Q_INVOKABLE void setLifecycleExemptAppids(const QStringList &appIds);
100108
109+ bool autohideLauncher() const;
110+ Q_INVOKABLE void setAutohideLauncher(bool autohideLauncher);
111+
112+ int launcherWidth() const;
113+ Q_INVOKABLE void setLauncherWidth(int launcherWidth);
114+
101115Q_SIGNALS:
102116 void pictureUriChanged(const QString&);
103117 void usageModeChanged(const QString&);
104118 void lockedOutTimeChanged(qint64 timestamp);
105119 void lifecycleExemptAppidsChanged(const QStringList&);
120+ void autohideLauncherChanged(bool autohideLauncher);
121+ void launcherWidthChanged(int launcherWidth);
106122
107123private:
108124 GSettingsControllerQml();
@@ -111,6 +127,8 @@ private:
111127 QString m_usageMode;
112128 qint64 m_lockedOutTime;
113129 QStringList m_lifecycleExemptAppids;
130+ bool m_autohideLauncher;
131+ int m_launcherWidth;
114132
115133 static GSettingsControllerQml* s_controllerInstance;
116134 QList<GSettingsQml *> m_registeredGSettings;
tests/qmltests/Launcher/tst_Launcher.qml+110−25
@@ -28,8 +28,8 @@ import Utils 0.1 // For EdgeBarrierSettings
2828 launcher. */
2929Item {
3030 id: root
31− width: units.gu(50)
32− height: units.gu(55)
31+ width: units.gu(140)
32+ height: units.gu(70)
3333
3434 Loader {
3535 id: launcherLoader
@@ -79,11 +79,40 @@ Item {
7979 }
8080 }
8181
82+ Binding {
83+ target: launcherLoader.item
84+ property: "lockedVisible"
85+ value: lockedVisibleCheckBox.checked
86+ }
87+ Binding {
88+ target: launcherLoader.item
89+ property: "panelWidth"
90+ value: units.gu(Math.round(widthSlider.value))
91+ }
92+
8293 ColumnLayout {
8394 anchors { bottom: parent.bottom; right: parent.right; margins: units.gu(1) }
8495 spacing: units.gu(1)
8596 width: childrenRect.width
8697
98+ RowLayout {
99+ CheckBox {
100+ id: lockedVisibleCheckBox
101+ checked: false
102+ }
103+ Label {
104+ text: "Launcher always visible"
105+ }
106+ }
107+
108+ Slider {
109+ id: widthSlider
110+ Layout.fillWidth: true
111+ minimumValue: 6
112+ maximumValue: 12
113+ value: 10
114+ }
115+
87116 MouseTouchEmulationCheckbox {}
88117
89118 EdgeBarrierControls {
@@ -217,10 +246,6 @@ Item {
217246 // growing while populating it with icons etc.
218247 tryCompare(listView, "flicking", false);
219248
220− // Make sure noone changed the height of the window. The issue this test case
221− // is verifying only happens on certain heights of the Launcher
222− compare(root.height, units.gu(55));
223−
224249 compare(listView.contentY, -listView.topMargin, "Launcher did not start up with first item unfolded");
225250
226251 // Now do check that snapping is in fact enabled
@@ -277,23 +302,31 @@ Item {
277302
278303 function positionLauncherListAtBeginning() {
279304 var listView = testCase.findChild(launcherLoader.item, "launcherListView");
280− listView.contentY = -listView.topMargin;
305+ var moveAnimation = findInvisibleChild(listView, "moveAnimation")
306+
307+ listView.moveToIndex(0);
308+
309+ waitForRendering(listView);
310+ tryCompare(moveAnimation, "running", false);
281311 }
282312 function positionLauncherListAtEnd() {
283313 var listView = testCase.findChild(launcherLoader.item, "launcherListView");
284− if ((listView.contentHeight + listView.topMargin + listView.bottomMargin) > listView.height) {
285− listView.contentY = listView.topMargin + listView.contentHeight
286− - listView.height;
287− }
314+ var moveAnimation = findInvisibleChild(listView, "moveAnimation")
315+
316+ listView.moveToIndex(listView.count -1);
317+
318+ waitForRendering(listView);
319+ tryCompare(moveAnimation, "running", false);
288320 }
289321
290322 function assertFocusOnIndex(index) {
291− var launcherPanel = findChild(launcherLoader.item, "launcherPanel");
292− var launcherListView = findChild(launcherLoader.item, "launcherListView");
293− var bfbFocusHighlight = findChild(launcherLoader.item, "bfbFocusHighlight");
294− waitForRendering(launcherLoader.item);
323+ var launcherPanel = findChild(launcher, "launcherPanel");
324+ var launcherListView = findChild(launcher, "launcherListView");
325+ var bfbFocusHighlight = findChild(launcher, "bfbFocusHighlight");
326+
327+ waitForRendering(launcher);
295328 tryCompare(launcherPanel, "highlightIndex", index);
296− tryCompare(bfbFocusHighlight, "visible", index === -1);
329+ compare(bfbFocusHighlight.visible, index === -1);
297330 for (var i = 0; i < launcherListView.count; i++) {
298331 var item = findChild(launcher, "launcherDelegate" + i);
299332 // Delegates might be destroyed when not visible. We can't check if they paint a focus highlight.
@@ -406,6 +439,7 @@ Item {
406439 wait(100)
407440 compare(launcher.maxPanelX, -launcher.panelWidth, "Launcher moved even if it shouldn't")
408441 }
442+
409443 waitUntilLauncherDisappears();
410444 launcher.available = true;
411445 }
@@ -429,6 +463,8 @@ Item {
429463 dragLauncherIntoView();
430464 var launcherListView = findChild(launcher, "launcherListView");
431465 for (var i = 0; i < launcherListView.count; ++i) {
466+ launcherListView.moveToIndex(i);
467+ waitForRendering(launcherListView);
432468 var delegate = findChild(launcherListView, "launcherDelegate" + i)
433469 compare(findChild(delegate, "countEmblem").visible, LauncherModel.get(i).countVisible)
434470 // Intentionally allow type coercion (string/number)
@@ -489,6 +525,7 @@ Item {
489525 launcher.lastSelectedApplication = "";
490526 dragLauncherIntoView();
491527 var listView = findChild(launcher, "launcherListView");
528+ var moveAnimation = findInvisibleChild(listView, "moveAnimation")
492529
493530 // flicking is unreliable. sometimes it works, sometimes the
494531 // list view moves just a tiny bit or not at all, making tests fail.
@@ -499,12 +536,14 @@ Item {
499536 } else {
500537 positionLauncherListAtEnd();
501538 }
502− tryCompare(listView, "flicking", false);
503−
504539 var oldY = listView.contentY;
505540
506541 mouseClick(listView, listView.width / 2, data.clickY);
507− tryCompare(listView, "flicking", false);
542+
543+ if (data.expectFlick) {
544+ tryCompare(moveAnimation, "running", true);
545+ }
546+ tryCompare(moveAnimation, "running", false);
508547
509548 if (data.expectFlick) {
510549 verify(listView.contentY != oldY);
@@ -1092,11 +1131,14 @@ Item {
10921131 function test_keyboardNavigation() {
10931132 var bfbFocusHighlight = findChild(launcher, "bfbFocusHighlight");
10941133 var quickList = findChild(launcher, "quickList");
1134+ var launcherPanel = findChild(launcher, "launcherPanel");
10951135 var launcherListView = findChild(launcher, "launcherListView");
10961136 var last = launcherListView.count - 1;
10971137
10981138 compare(bfbFocusHighlight.visible, false);
10991139 launcher.openForKeyboardNavigation();
1140+ tryCompare(launcherPanel, "x", 0);
1141+ waitForRendering(launcher);
11001142
11011143 assertFocusOnIndex(-1);
11021144
@@ -1173,11 +1215,47 @@ Item {
11731215 compare(signalSpy.count, 1, "Quicklist signal wasn't triggered")
11741216 compare(signalSpy.signalArguments[0][0], LauncherModel.get(1).appId)
11751217 compare(signalSpy.signalArguments[0][1], 2)
1218+ assertFocusOnIndex(-2);
1219+ }
1220+
1221+ function test_hideNotWorkingWhenLockedOut_data() {
1222+ return [
1223+ {tag: "locked visible", locked: true},
1224+ {tag: "no locked visible", locked: false},
1225+ ]
1226+ }
1227+
1228+ function test_hideNotWorkingWhenLockedOut(data) {
1229+ launcher.lockedVisible = data.locked;
1230+ if (data.locked) {
1231+ tryCompare(launcher, "state", "visible");
1232+ } else {
1233+ tryCompare(launcher, "state", "");
1234+ }
1235+
1236+ launcher.hide();
1237+ waitForRendering(launcher);
1238+ if (data.locked) {
1239+ verify(launcher.state == "visible");
1240+ } else {
1241+ verify(launcher.state == "");
1242+ }
11761243 }
11771244
1178− function test_cancelKbdNavigationWitMouse() {
1245+ function test_cancelKbdNavigationWitMouse_data() {
1246+ return [
1247+ {tag: "locked out - no quicklist", autohide: false, withQuickList: false },
1248+ {tag: "locked out - with quicklist", autohide: false, withQuickList: true },
1249+ {tag: "autohide - no quicklist", autohide: true, withQuickList: false },
1250+ {tag: "autohide - with quicklist", autohide: true, withQuickList: true },
1251+ ]
1252+ }
1253+
1254+ function test_cancelKbdNavigationWitMouse(data) {
1255+ launcher.autohideEnabled = data.autohide;
11791256 launcher.openForKeyboardNavigation();
11801257 waitForRendering(launcher);
1258+
11811259 var launcherPanel = findChild(launcher, "launcherPanel");
11821260 tryCompare(launcherPanel, "x", 0);
11831261
@@ -1185,15 +1263,22 @@ Item {
11851263
11861264 keyClick(Qt.Key_Down); // Down to launcher item 0
11871265 keyClick(Qt.Key_Down); // Down to launcher item 1
1188− keyClick(Qt.Key_Right); // Into quicklist
11891266
1267+ if (data.withQuickList) {
1268+ keyClick(Qt.Key_Right); // Into quicklist
1269+ tryCompare(quickList, "visible", true)
1270+ }
11901271 waitForRendering(launcher)
1191− tryCompare(quickList, "visible", true)
11921272
1193− mouseClick(root, root.width / 2, units.gu(2));
1273+ mouseClick(root);
11941274
1195− tryCompare(launcher, "state", "");
1196− tryCompare(launcherPanel, "highlightIndex", -2);
1275+ if (data.autohide) {
1276+ tryCompare(launcher, "state", "");
1277+ } else {
1278+ tryCompare(launcher, "state", "visible");
1279+ }
1280+
1281+ assertFocusOnIndex(-2);
11971282 }
11981283 }
11991284}
tests/qmltests/tst_Shell.qml+92−13
@@ -223,6 +223,19 @@ Rectangle {
223223 }
224224 }
225225
226+ Row {
227+ anchors { left: parent.left; right: parent.right }
228+ CheckBox {
229+ id: autohideLauncherCheckbox
230+ onCheckedChanged: {
231+ GSettingsController.setAutohideLauncher(checked)
232+ }
233+ }
234+ Label {
235+ text: "Autohide launcher"
236+ }
237+ }
238+
226239 Label { text: "Applications"; font.bold: true }
227240
228241 Button {
@@ -921,6 +934,7 @@ Rectangle {
921934 function dragLauncherIntoView() {
922935 var launcher = findChild(shell, "launcher");
923936 var launcherPanel = findChild(launcher, "launcherPanel");
937+ waitForRendering(launcher);
924938 verify(launcherPanel.x = - launcherPanel.width);
925939
926940 var touchStartX = 2;
@@ -1678,7 +1692,7 @@ Rectangle {
16781692 var x = 0;
16791693 var y = shell.height * .5
16801694 mouseMove(shell, x, y)
1681− while (x <= spreadFlickable.width) {
1695+ while (x <= shell.width) {
16821696 x+=10;
16831697 mouseMove(shell, x, y)
16841698 wait(0); // spin the loop so bindings get evaluated
@@ -1752,25 +1766,38 @@ Rectangle {
17521766 keyRelease(Qt.Key_Alt);
17531767 }
17541768
1755− function test_focusAppFromLauncherExitsSpread() {
1756− loadDesktopShellWithApps()
1769+ function test_focusAppFromLauncherExitsSpread_data() {
1770+ return [
1771+ {tag: "autohide launcher", launcherLocked: false },
1772+ {tag: "locked launcher", launcherLocked: true }
1773+ ]
1774+ }
17571775
1758− var desktopSpread = findChild(shell, "spread");
1776+ function test_focusAppFromLauncherExitsSpread(data) {
1777+ loadDesktopShellWithApps()
17591778 var launcher = findChild(shell, "launcher");
1779+ var desktopSpread = findChild(shell, "spread");
17601780 var bfb = findChild(launcher, "buttonShowDashHome");
17611781
1782+ GSettingsController.setAutohideLauncher(!data.launcherLocked);
1783+ waitForRendering(shell);
1784+
17621785 keyPress(Qt.Key_Alt)
17631786 keyClick(Qt.Key_Tab);
17641787
17651788 tryCompare(desktopSpread, "state", "altTab")
17661789
1767− revealLauncherByEdgePushWithMouse();
1768− tryCompare(launcher, "x", 0);
1769− mouseMove(bfb, bfb.width / 2, bfb.height / 2)
1770− waitForRendering(shell)
1790+ if (!data.launcherLocked) {
1791+ revealLauncherByEdgePushWithMouse();
1792+ tryCompare(launcher, "x", 0);
1793+ mouseMove(bfb, bfb.width / 2, bfb.height / 2)
1794+ waitForRendering(shell)
1795+ }
17711796
17721797 mouseClick(bfb, bfb.width / 2, bfb.height / 2)
1773− tryCompare(launcher, "state", "")
1798+ if (!data.launcherLocked) {
1799+ tryCompare(launcher, "state", "")
1800+ }
17741801 tryCompare(desktopSpread, "state", "")
17751802
17761803 tryCompare(ApplicationManager, "focusedApplicationId", "unity8-dash")
@@ -1998,16 +2025,24 @@ Rectangle {
19982025 }
19992026 }
20002027
2001− function test_superTabToCycleLauncher() {
2028+ function test_superTabToCycleLauncher_data() {
2029+ return [
2030+ {tag: "autohide launcher", launcherLocked: false},
2031+ {tag: "locked launcher", launcherLocked: true}
2032+ ]
2033+ }
2034+
2035+ function test_superTabToCycleLauncher(data) {
20022036 loadShell("desktop");
20032037 shell.usageScenario = "desktop";
2038+ GSettingsController.setAutohideLauncher(!data.launcherLocked);
20042039 waitForRendering(shell);
20052040
20062041 var launcher = findChild(shell, "launcher");
20072042 var launcherPanel = findChild(launcher, "launcherPanel");
20082043 var firstAppInLauncher = LauncherModel.get(0).appId;
20092044
2010− compare(launcher.state, "");
2045+ compare(launcher.state, data.launcherLocked ? "visible": "");
20112046 compare(launcherPanel.highlightIndex, -2);
20122047 compare(ApplicationManager.focusedApplicationId, "unity8-dash");
20132048
@@ -2019,7 +2054,7 @@ Rectangle {
20192054 keyClick(Qt.Key_Tab);
20202055 tryCompare(launcherPanel, "highlightIndex", 0);
20212056 keyRelease(Qt.Key_Super_L, Qt.MetaModifier);
2022− tryCompare(launcher, "state", "");
2057+ tryCompare(launcher, "state", data.launcherLocked ? "visible" : "");
20232058 tryCompare(launcherPanel, "highlightIndex", -2);
20242059 tryCompare(ApplicationManager, "focusedApplicationId", firstAppInLauncher);
20252060
@@ -2029,7 +2064,7 @@ Rectangle {
20292064 tryCompare(launcher, "state", "visible");
20302065 tryCompare(launcherPanel, "highlightIndex", -1);
20312066 keyRelease(Qt.Key_Super_L, Qt.MetaModifier);
2032− tryCompare(launcher, "state", "");
2067+ tryCompare(launcher, "state", data.launcherLocked ? "visible" : "");
20332068 tryCompare(launcherPanel, "highlightIndex", -2);
20342069 tryCompare(ApplicationManager, "focusedApplicationId", "unity8-dash");
20352070 }
@@ -2078,6 +2113,50 @@ Rectangle {
20782113 tryCompare(launcher, "focus", true)
20792114 }
20802115
2116+ function test_lockedOutLauncherAddsMarginsToMaximized() {
2117+ loadShell("desktop");
2118+ shell.usageScenario = "desktop";
2119+ waitForRendering(shell);
2120+ var appContainer = findChild(shell, "appContainer");
2121+ var launcher = findChild(shell, "launcher");
2122+
2123+ var app = ApplicationManager.startApplication("music-app");
2124+ waitUntilAppWindowIsFullyLoaded(app);
2125+ var appDelegate = findChild(appContainer, "appDelegate_music-app");
2126+ appDelegate.maximize();
2127+ tryCompare(appDelegate, "visuallyMaximized", true);
2128+ waitForRendering(shell);
2129+
2130+ GSettingsController.setAutohideLauncher(true);
2131+ waitForRendering(shell)
2132+ var hiddenSize = appDelegate.width;
2133+
2134+ GSettingsController.setAutohideLauncher(false);
2135+ waitForRendering(shell)
2136+ var shownSize = appDelegate.width;
2137+
2138+ compare(shownSize + launcher.panelWidth, hiddenSize);
2139+ }
2140+
2141+ function test_fullscreenAppHidesLockedOutLauncher() {
2142+ loadShell("desktop");
2143+ shell.usageScenario = "desktop";
2144+
2145+ var launcher = findChild(shell, "launcher");
2146+ var launcherPanel = findChild(launcher, "launcherPanel");
2147+
2148+ GSettingsController.setAutohideLauncher(false);
2149+ waitForRendering(shell)
2150+
2151+ tryCompare(launcher, "lockedVisible", true);
2152+
2153+ var cameraApp = ApplicationManager.startApplication("camera-app");
2154+ waitUntilAppWindowIsFullyLoaded(cameraApp);
2155+
2156+ tryCompare(launcher, "lockedVisible", false);
2157+ }
2158+
2159+
20812160 function test_inputEventsOnEdgesEndUpInAppSurface_data() {
20822161 return [
20832162 { tag: "phone", repeaterName: "spreadRepeater" },