on fa24e91 help sign in
unity8/Shell.qml
632 lines 22 kB history raw
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 */
17import QtQuick 2.0
18import Ubuntu.Application 0.1
19import Ubuntu.Components 0.1
20import Ubuntu.Gestures 0.1
21import LightDM 0.1 as LightDM
22import "Dash"
23import "Greeter"
24import "Launcher"
25import "Panel"
26import "Hud"
27import "Components"
28import "Components/Math.js" as MathLocal
29import "Bottombar"
30import "SideStage"
31import "Notifications"
32import Unity.Notifications 1.0 as NotificationBackend
34FocusScope {
35 id: shell
37 // this is only here to select the width / height of the window if not running fullscreen
38 property bool tablet: false
39 width: tablet ? units.gu(160) : units.gu(40)
40 height: tablet ? units.gu(100) : units.gu(71)
42 property real edgeSize: units.gu(2)
43 property url default_background: shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg"
44 property url background: default_background
45 readonly property real panelHeight: panel.panelHeight
47 property bool dashShown: dash.shown
48 property bool stageScreenshotsReady: {
49 if (sideStage.shown) {
50 if (mainStage.applications.count > 0) {
51 return mainStage.usingScreenshots && sideStage.usingScreenshots;
52 } else {
53 return sideStage.usingScreenshots;
54 }
55 } else {
56 return mainStage.usingScreenshots;
57 }
58 }
60 property ListModel searchHistory: SearchHistoryModel {}
62 property var applicationManager: ApplicationManagerWrapper {}
64 Component.onCompleted: {
65 applicationManager.sideStageEnabled = Qt.binding(function() { return sideStage.enabled })
67 // FIXME: if application focused before shell starts, shell draws on top of it only.
68 // We should detect already running applications on shell start and bring them to the front.
69 applicationManager.unfocusCurrentApplication();
70 }
72 readonly property bool fullscreenMode: {
73 if (greeter.shown || lockscreen.shown) {
74 return false;
75 } else if (mainStage.usingScreenshots) { // Window Manager animating so want to re-evaluate fullscreen mode
76 return mainStage.switchingFromFullscreenToFullscreen;
77 } else if (applicationManager.mainStageFocusedApplication) {
78 return applicationManager.mainStageFocusedApplication.fullscreen;
79 } else {
80 return false;
81 }
82 }
84 Connections {
85 target: applicationManager
86 ignoreUnknownSignals: true
87 onFocusRequested: {
88 // TODO: this should be protected to only unlock for certain applications / certain usecases
89 // potentially only in connection with a notification
90 greeter.hide();
91 shell.activateApplication(desktopFile);
92 }
93 }
95 function activateApplication(desktopFile, argument) {
96 if (applicationManager) {
97 // For newly started applications, as it takes them time to draw their first frame
98 // we add a delay before we hide the animation screenshots to compensate.
99 var addDelay = !applicationManager.getApplicationFromDesktopFile(desktopFile);
101 var application;
102 application = applicationManager.activateApplication(desktopFile, argument);
103 if (application == null) {
104 return;
105 }
106 if (application.stage == ApplicationInfo.MainStage || !sideStage.enabled) {
107 mainStage.activateApplication(desktopFile, addDelay);
108 } else {
109 sideStage.activateApplication(desktopFile, addDelay);
110 }
111 stages.show();
112 }
113 }
115 VolumeControl {
116 id: volumeControl
117 }
119 Keys.onVolumeUpPressed: volumeControl.volumeUp()
120 Keys.onVolumeDownPressed: volumeControl.volumeDown()
122 Keys.onReleased: {
123 if (event.key == Qt.Key_PowerOff) {
124 greeter.show()
125 }
126 }
128 Item {
129 id: underlay
130 anchors.fill: parent
131 visible: !(panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth)
132 && (stages.fullyHidden
133 || (stages.fullyShown && mainStage.usingScreenshots)
134 || !stages.fullyShown && (mainStage.usingScreenshots || (sideStage.shown && sideStage.usingScreenshots)))
136 Image {
137 id: backgroundImage
138 source: shell.background
139 sourceSize.width: parent.width
140 sourceSize.height: parent.height
141 anchors.fill: parent
142 }
144 Rectangle {
145 anchors.fill: parent
146 color: "black"
147 opacity: dash.disappearingAnimationProgress
148 }
150 Dash {
151 id: dash
153 available: !greeter.shown && !lockscreen.shown
154 hides: [stages, launcher, panel.indicators]
155 shown: disappearingAnimationProgress !== 1.0
156 enabled: disappearingAnimationProgress === 0.0
157 // FIXME: unfocus all applications when going back to the dash
158 onEnabledChanged: {
159 if (enabled) {
160 shell.applicationManager.unfocusCurrentApplication()
161 }
162 }
164 anchors {
165 fill: parent
166 topMargin: panel.panelHeight
167 }
169 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
170 opacity: 1.0 - disappearingAnimationProgress
171 property real disappearingAnimationProgress: {
172 if (greeter.shown) {
173 return greeter.showProgress;
174 } else {
175 return stagesOuterContainer.showProgress;
176 }
177 }
179 // FIXME: only necessary because stagesRevealer.animatedProgress and
180 // greeterRevealer.animatedProgress are not animated
181 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
182 }
183 }
186 Item {
187 id: stagesOuterContainer
189 width: parent.width
190 height: parent.height
191 x: launcher.progress
192 Behavior on x {SmoothedAnimation{velocity: 600}}
194 property real showProgress:
195 MathLocal.clamp(1 - (x + stages.x) / shell.width, 0, 1)
197 Showable {
198 id: stages
200 x: width
202 property bool fullyShown: shown && x == 0 && parent.x == 0
203 property bool fullyHidden: !shown && x == width
204 available: !greeter.shown
205 hides: [launcher, panel.indicators]
206 shown: false
207 opacity: 1.0
208 showAnimation: StandardAnimation { property: "x"; duration: 350; to: 0; easing.type: Easing.OutCubic }
209 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: width; easing.type: Easing.OutCubic }
211 width: parent.width
212 height: parent.height
214 // close the stages when no focused application remains
215 Connections {
216 target: shell.applicationManager
217 onMainStageFocusedApplicationChanged: stages.closeIfNoApplications()
218 onSideStageFocusedApplicationChanged: stages.closeIfNoApplications()
219 ignoreUnknownSignals: true
220 }
222 function closeIfNoApplications() {
223 if (!shell.applicationManager.mainStageFocusedApplication
224 && !shell.applicationManager.sideStageFocusedApplication
225 && shell.applicationManager.mainStageApplications.count == 0
226 && shell.applicationManager.sideStageApplications.count == 0) {
227 stages.hide();
228 }
229 }
231 // show the stages when an application gets the focus
232 Connections {
233 target: shell.applicationManager
234 onMainStageFocusedApplicationChanged: {
235 if (shell.applicationManager.mainStageFocusedApplication) {
236 mainStage.show();
237 stages.show();
238 }
239 }
240 onSideStageFocusedApplicationChanged: {
241 if (shell.applicationManager.sideStageFocusedApplication) {
242 sideStage.show();
243 stages.show();
244 }
245 }
246 ignoreUnknownSignals: true
247 }
250 Stage {
251 id: mainStage
253 anchors.fill: parent
254 fullyShown: stages.fullyShown
255 shouldUseScreenshots: !fullyShown
256 rightEdgeEnabled: !sideStage.enabled
258 applicationManager: shell.applicationManager
259 rightEdgeDraggingAreaWidth: shell.edgeSize
260 normalApplicationY: shell.panelHeight
262 shown: true
263 function show() {
264 stages.show();
265 }
266 function hide() {
267 }
269 // FIXME: workaround the fact that focusing a main stage application
270 // raises its surface on top of all other surfaces including the ones
271 // that belong to side stage applications.
272 onFocusedApplicationChanged: {
273 if (focusedApplication && sideStage.focusedApplication && sideStage.fullyShown) {
274 shell.applicationManager.focusApplication(sideStage.focusedApplication);
275 }
276 }
277 }
279 SideStage {
280 id: sideStage
282 applicationManager: shell.applicationManager
283 rightEdgeDraggingAreaWidth: shell.edgeSize
284 normalApplicationY: shell.panelHeight
286 onShownChanged: {
287 if (!shown && mainStage.applications.count == 0) {
288 stages.hide();
289 }
290 }
291 // FIXME: when hiding the side stage, refocus the main stage
292 // application so that it goes in front of the side stage
293 // application and hides it
294 onFullyShownChanged: {
295 if (!fullyShown && stages.fullyShown && sideStage.focusedApplication != null) {
296 shell.applicationManager.focusApplication(mainStage.focusedApplication);
297 }
298 }
300 enabled: shell.width >= units.gu(60)
301 visible: enabled
302 fullyShown: stages.fullyShown && shown
303 && sideStage[sideStageRevealer.boundProperty] == sideStageRevealer.openedValue
304 shouldUseScreenshots: !fullyShown || mainStage.usingScreenshots || sideStageRevealer.pressed
306 available: !greeter.shown && !lockscreen.shown && enabled
307 hides: [launcher, panel.indicators]
308 shown: false
309 showAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.openedValue; easing.type: Easing.OutQuint }
310 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.closedValue; easing.type: Easing.OutQuint }
312 width: units.gu(40)
313 height: stages.height
314 handleExpanded: sideStageRevealer.pressed
315 }
317 Revealer {
318 id: sideStageRevealer
320 enabled: mainStage.applications.count > 0 && sideStage.applications.count > 0
321 && sideStage.available
322 direction: Qt.RightToLeft
323 openedValue: parent.width - sideStage.width
324 hintDisplacement: units.gu(3)
325 /* The size of the sidestage handle needs to be bigger than the
326 typical size used for edge detection otherwise it is really
327 hard to grab.
328 */
329 handleSize: sideStage.shown ? units.gu(4) : shell.edgeSize
330 closedValue: parent.width + sideStage.handleSizeCollapsed
331 target: sideStage
332 x: parent.width - width
333 width: sideStage.width + handleSize * 0.7
334 height: sideStage.height
335 orientation: Qt.Horizontal
336 }
338 DragHandle {
339 id: stagesRevealer
341 anchors.top: parent.top
342 anchors.bottom: parent.bottom
343 anchors.right: parent.left
345 width: shell.edgeSize
346 direction: Direction.Leftwards
347 enabled: mainStage.applications.count > 0 || sideStage.applications.count > 0
348 }
349 }
350 }
352 Lockscreen {
353 id: lockscreen
354 hides: [launcher, panel.indicators, hud]
355 shown: false
356 enabled: true
357 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
358 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
359 y: panel.panelHeight
360 x: required ? 0 : - width
361 width: parent.width
362 height: parent.height - panel.panelHeight
363 background: shell.background
365 onUnlocked: lockscreen.hide()
366 onCancel: greeter.show()
368 Component.onCompleted: {
369 if (LightDM.Users.count == 1) {
370 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
371 }
372 }
373 }
375 Connections {
376 target: LightDM.Greeter
378 onShowPrompt: {
379 if (LightDM.Users.count == 1) {
380 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
381 if (text == "PIN") {
382 lockscreen.alphaNumeric = false
383 } else {
384 lockscreen.alphaNumeric = true
385 }
386 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
387 lockscreen.show();
388 }
389 }
390 }
392 Greeter {
393 id: greeter
395 available: true
396 hides: [launcher, panel.indicators, hud]
397 shown: true
399 y: panel.panelHeight
400 width: parent.width
401 height: parent.height - panel.panelHeight
403 dragHandleWidth: shell.edgeSize
405 property var previousMainApp: null
406 property var previousSideApp: null
408 onShownChanged: {
409 if (shown) {
410 lockscreen.reset();
411 // If there is only one user, we start authenticating with that one here.
412 // If there are more users, the Greeter will handle that
413 if (LightDM.Users.count == 1) {
414 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
415 }
416 greeter.forceActiveFocus();
417 // FIXME: *FocusedApplication are not updated when unfocused, hence the need to check whether
418 // the stage was actually shown
419 if (mainStage.fullyShown) greeter.previousMainApp = applicationManager.mainStageFocusedApplication;
420 if (sideStage.fullyShown) greeter.previousSideApp = applicationManager.sideStageFocusedApplication;
421 applicationManager.unfocusCurrentApplication();
422 } else {
423 if (greeter.previousMainApp) {
424 applicationManager.focusApplication(greeter.previousMainApp);
425 greeter.previousMainApp = null;
426 }
427 if (greeter.previousSideApp) {
428 applicationManager.focusApplication(greeter.previousSideApp);
429 greeter.previousSideApp = null;
430 }
431 }
432 }
434 onUnlocked: greeter.hide()
435 onSelected: {
436 var bgPath = greeter.model.data(uid, LightDM.UserRoles.BackgroundPathRole)
437 shell.background = bgPath ? bgPath : default_background
438 }
440 onLeftTeaserPressedChanged: {
441 if (leftTeaserPressed) {
442 launcher.tease();
443 }
444 }
445 }
447 InputFilterArea {
448 anchors.fill: parent
449 blockInput: greeter.shown || lockscreen.shown
450 }
452 Item {
453 id: overlay
455 anchors.fill: parent
457 Panel {
458 id: panel
459 anchors.fill: parent //because this draws indicator menus
460 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
461 indicators {
462 hides: [launcher]
463 }
464 fullscreenMode: shell.fullscreenMode
465 searchVisible: !greeter.shown && !lockscreen.shown
467 InputFilterArea {
468 anchors.fill: parent
469 blockInput: panel.indicators.shown
470 }
471 }
473 Hud {
474 id: hud
476 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
477 height: parent.height
479 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown
480 shown: false
481 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
482 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
484 Connections {
485 target: shell.applicationManager
486 onMainStageFocusedApplicationChanged: hud.hide()
487 onSideStageFocusedApplicationChanged: hud.hide()
488 }
490 InputFilterArea {
491 anchors.fill: parent
492 blockInput: hud.shown
493 }
494 }
496 Revealer {
497 id: hudRevealer
499 enabled: hud.shown
500 width: hud.width
501 anchors.left: hud.left
502 height: parent.height
503 target: hud.revealerTarget
504 closedValue: height
505 openedValue: 0
506 direction: Qt.RightToLeft
507 orientation: Qt.Vertical
508 handleSize: hud.handleHeight
509 onCloseClicked: target.hide()
510 }
512 Bottombar {
513 theHud: hud
514 anchors.fill: parent
515 enabled: !panel.indicators.shown
516 applicationIsOnForeground: applicationManager.mainStageFocusedApplication
517 || applicationManager.sideStageFocusedApplication
518 }
520 InputFilterArea {
521 blockInput: launcher.shown
522 anchors {
523 top: parent.top
524 bottom: parent.bottom
525 left: parent.left
526 }
527 width: launcher.width
528 }
530 Launcher {
531 id: launcher
533 anchors.top: parent.top
534 anchors.bottom: parent.bottom
535 width: parent.width
536 dragAreaWidth: shell.edgeSize
537 available: !greeter.locked
538 onDashItemSelected: {
539 greeter.hide()
540 // Animate if moving between application and dash
541 if (!stages.shown) {
542 dash.setCurrentScope("home.scope", true, false)
543 } else {
544 dash.setCurrentScope("home.scope", false, false)
545 }
546 stages.hide();
547 }
548 onDash: {
549 if (stages.shown) {
550 dash.setCurrentScope("applications.scope", true, false)
551 stages.hide();
552 launcher.hide();
553 }
554 }
555 onLauncherApplicationSelected:{
556 greeter.hide()
557 shell.activateApplication(desktopFile)
558 }
559 onShownChanged: {
560 if (shown) {
561 panel.indicators.hide()
562 hud.hide()
563 }
564 }
565 }
567 Notifications {
568 id: notifications
570 model: NotificationBackend.Model
571 anchors {
572 top: parent.top
573 right: parent.right
574 bottom: parent.bottom
575 leftMargin: units.gu(1)
576 rightMargin: units.gu(1)
577 topMargin: panel.panelHeight + units.gu(1)
578 }
579 states: [
580 State {
581 name: "narrow"
582 when: overlay.width <= units.gu(60)
583 AnchorChanges { target: notifications; anchors.left: parent.left }
584 },
585 State {
586 name: "wide"
587 when: overlay.width > units.gu(60)
588 AnchorChanges { target: notifications; anchors.left: undefined }
589 PropertyChanges { target: notifications; width: units.gu(38) }
590 }
591 ]
592 }
593 }
595 focus: true
597 InputFilterArea {
598 anchors {
599 top: parent.top
600 bottom: parent.bottom
601 left: parent.left
602 }
603 width: shell.edgeSize
604 blockInput: true
605 }
607 InputFilterArea {
608 anchors {
609 top: parent.top
610 bottom: parent.bottom
611 right: parent.right
612 }
613 width: shell.edgeSize
614 blockInput: true
615 }
617 Binding {
618 target: i18n
619 property: "domain"
620 value: "unity8"
621 }
623 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
624 MouseArea {
625 anchors.bottom: parent.bottom
626 anchors.left: parent.left
627 anchors.right: parent.right
628 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
630 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
631 }