on 44444ad help sign in
unity8/Shell.qml
634 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 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
67 applicationManager.sideStageEnabled = Qt.binding(function() { return sideStage.enabled })
69 // FIXME: if application focused before shell starts, shell draws on top of it only.
70 // We should detect already running applications on shell start and bring them to the front.
71 applicationManager.unfocusCurrentApplication();
72 }
74 readonly property bool fullscreenMode: {
75 if (greeter.shown || lockscreen.shown) {
76 return false;
77 } else if (mainStage.usingScreenshots) { // Window Manager animating so want to re-evaluate fullscreen mode
78 return mainStage.switchingFromFullscreenToFullscreen;
79 } else if (applicationManager.mainStageFocusedApplication) {
80 return applicationManager.mainStageFocusedApplication.fullscreen;
81 } else {
82 return false;
83 }
84 }
86 Connections {
87 target: applicationManager
88 ignoreUnknownSignals: true
89 onFocusRequested: {
90 // TODO: this should be protected to only unlock for certain applications / certain usecases
91 // potentially only in connection with a notification
92 greeter.hide();
93 shell.activateApplication(desktopFile);
94 }
95 }
97 function activateApplication(desktopFile, argument) {
98 if (applicationManager) {
99 // For newly started applications, as it takes them time to draw their first frame
100 // we add a delay before we hide the animation screenshots to compensate.
101 var addDelay = !applicationManager.getApplicationFromDesktopFile(desktopFile);
103 var application;
104 application = applicationManager.activateApplication(desktopFile, argument);
105 if (application == null) {
106 return;
107 }
108 if (application.stage == ApplicationInfo.MainStage || !sideStage.enabled) {
109 mainStage.activateApplication(desktopFile, addDelay);
110 } else {
111 sideStage.activateApplication(desktopFile, addDelay);
112 }
113 stages.show();
114 }
115 }
117 VolumeControl {
118 id: volumeControl
119 }
121 Keys.onVolumeUpPressed: volumeControl.volumeUp()
122 Keys.onVolumeDownPressed: volumeControl.volumeDown()
124 Keys.onReleased: {
125 if (event.key == Qt.Key_PowerOff) {
126 greeter.show()
127 }
128 }
130 Item {
131 id: underlay
132 anchors.fill: parent
133 visible: !(panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth)
134 && (stages.fullyHidden
135 || (stages.fullyShown && mainStage.usingScreenshots)
136 || !stages.fullyShown && (mainStage.usingScreenshots || (sideStage.shown && sideStage.usingScreenshots)))
138 Image {
139 id: backgroundImage
140 source: shell.background
141 sourceSize.width: parent.width
142 sourceSize.height: parent.height
143 anchors.fill: parent
144 }
146 Rectangle {
147 anchors.fill: parent
148 color: "black"
149 opacity: dash.disappearingAnimationProgress
150 }
152 Dash {
153 id: dash
155 available: !greeter.shown && !lockscreen.shown
156 hides: [stages, launcher, panel.indicators]
157 shown: disappearingAnimationProgress !== 1.0
158 enabled: disappearingAnimationProgress === 0.0
159 // FIXME: unfocus all applications when going back to the dash
160 onEnabledChanged: {
161 if (enabled) {
162 shell.applicationManager.unfocusCurrentApplication()
163 }
164 }
166 anchors {
167 fill: parent
168 topMargin: panel.panelHeight
169 }
171 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
172 opacity: 1.0 - disappearingAnimationProgress
173 property real disappearingAnimationProgress: {
174 if (greeter.shown) {
175 return greeter.showProgress;
176 } else {
177 return stagesOuterContainer.showProgress;
178 }
179 }
181 // FIXME: only necessary because stagesRevealer.animatedProgress and
182 // greeterRevealer.animatedProgress are not animated
183 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
184 }
185 }
188 Item {
189 id: stagesOuterContainer
191 width: parent.width
192 height: parent.height
193 x: launcher.progress
194 Behavior on x {SmoothedAnimation{velocity: 600}}
196 property real showProgress:
197 MathLocal.clamp(1 - (x + stages.x) / shell.width, 0, 1)
199 Showable {
200 id: stages
202 x: width
204 property bool fullyShown: shown && x == 0 && parent.x == 0
205 property bool fullyHidden: !shown && x == width
206 available: !greeter.shown
207 hides: [launcher, panel.indicators]
208 shown: false
209 opacity: 1.0
210 showAnimation: StandardAnimation { property: "x"; duration: 350; to: 0; easing.type: Easing.OutCubic }
211 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: width; easing.type: Easing.OutCubic }
213 width: parent.width
214 height: parent.height
216 // close the stages when no focused application remains
217 Connections {
218 target: shell.applicationManager
219 onMainStageFocusedApplicationChanged: stages.closeIfNoApplications()
220 onSideStageFocusedApplicationChanged: stages.closeIfNoApplications()
221 ignoreUnknownSignals: true
222 }
224 function closeIfNoApplications() {
225 if (!shell.applicationManager.mainStageFocusedApplication
226 && !shell.applicationManager.sideStageFocusedApplication
227 && shell.applicationManager.mainStageApplications.count == 0
228 && shell.applicationManager.sideStageApplications.count == 0) {
229 stages.hide();
230 }
231 }
233 // show the stages when an application gets the focus
234 Connections {
235 target: shell.applicationManager
236 onMainStageFocusedApplicationChanged: {
237 if (shell.applicationManager.mainStageFocusedApplication) {
238 mainStage.show();
239 stages.show();
240 }
241 }
242 onSideStageFocusedApplicationChanged: {
243 if (shell.applicationManager.sideStageFocusedApplication) {
244 sideStage.show();
245 stages.show();
246 }
247 }
248 ignoreUnknownSignals: true
249 }
252 Stage {
253 id: mainStage
255 anchors.fill: parent
256 fullyShown: stages.fullyShown
257 shouldUseScreenshots: !fullyShown
258 rightEdgeEnabled: !sideStage.enabled
260 applicationManager: shell.applicationManager
261 rightEdgeDraggingAreaWidth: shell.edgeSize
262 normalApplicationY: shell.panelHeight
264 shown: true
265 function show() {
266 stages.show();
267 }
268 function hide() {
269 }
271 // FIXME: workaround the fact that focusing a main stage application
272 // raises its surface on top of all other surfaces including the ones
273 // that belong to side stage applications.
274 onFocusedApplicationChanged: {
275 if (focusedApplication && sideStage.focusedApplication && sideStage.fullyShown) {
276 shell.applicationManager.focusApplication(sideStage.focusedApplication);
277 }
278 }
279 }
281 SideStage {
282 id: sideStage
284 applicationManager: shell.applicationManager
285 rightEdgeDraggingAreaWidth: shell.edgeSize
286 normalApplicationY: shell.panelHeight
288 onShownChanged: {
289 if (!shown && mainStage.applications.count == 0) {
290 stages.hide();
291 }
292 }
293 // FIXME: when hiding the side stage, refocus the main stage
294 // application so that it goes in front of the side stage
295 // application and hides it
296 onFullyShownChanged: {
297 if (!fullyShown && stages.fullyShown && sideStage.focusedApplication != null) {
298 shell.applicationManager.focusApplication(mainStage.focusedApplication);
299 }
300 }
302 enabled: shell.width >= units.gu(60)
303 visible: enabled
304 fullyShown: stages.fullyShown && shown
305 && sideStage[sideStageRevealer.boundProperty] == sideStageRevealer.openedValue
306 shouldUseScreenshots: !fullyShown || mainStage.usingScreenshots || sideStageRevealer.pressed
308 available: !greeter.shown && !lockscreen.shown && enabled
309 hides: [launcher, panel.indicators]
310 shown: false
311 showAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.openedValue; easing.type: Easing.OutQuint }
312 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.closedValue; easing.type: Easing.OutQuint }
314 width: units.gu(40)
315 height: stages.height
316 handleExpanded: sideStageRevealer.pressed
317 }
319 Revealer {
320 id: sideStageRevealer
322 enabled: mainStage.applications.count > 0 && sideStage.applications.count > 0
323 && sideStage.available
324 direction: Qt.RightToLeft
325 openedValue: parent.width - sideStage.width
326 hintDisplacement: units.gu(3)
327 /* The size of the sidestage handle needs to be bigger than the
328 typical size used for edge detection otherwise it is really
329 hard to grab.
330 */
331 handleSize: sideStage.shown ? units.gu(4) : shell.edgeSize
332 closedValue: parent.width + sideStage.handleSizeCollapsed
333 target: sideStage
334 x: parent.width - width
335 width: sideStage.width + handleSize * 0.7
336 height: sideStage.height
337 orientation: Qt.Horizontal
338 }
340 DragHandle {
341 id: stagesRevealer
343 anchors.top: parent.top
344 anchors.bottom: parent.bottom
345 anchors.right: parent.left
347 width: shell.edgeSize
348 direction: Direction.Leftwards
349 enabled: mainStage.applications.count > 0 || sideStage.applications.count > 0
350 }
351 }
352 }
354 Lockscreen {
355 id: lockscreen
356 hides: [launcher, panel.indicators, hud]
357 shown: false
358 enabled: true
359 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
360 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
361 y: panel.panelHeight
362 x: required ? 0 : - width
363 width: parent.width
364 height: parent.height - panel.panelHeight
365 background: shell.background
367 onUnlocked: lockscreen.hide()
368 onCancel: greeter.show()
370 Component.onCompleted: {
371 if (LightDM.Users.count == 1) {
372 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
373 }
374 }
375 }
377 Connections {
378 target: LightDM.Greeter
380 onShowPrompt: {
381 if (LightDM.Users.count == 1) {
382 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
383 if (text == "PIN") {
384 lockscreen.alphaNumeric = false
385 } else {
386 lockscreen.alphaNumeric = true
387 }
388 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
389 lockscreen.show();
390 }
391 }
392 }
394 Greeter {
395 id: greeter
397 available: true
398 hides: [launcher, panel.indicators, hud]
399 shown: true
401 y: panel.panelHeight
402 width: parent.width
403 height: parent.height - panel.panelHeight
405 dragHandleWidth: shell.edgeSize
407 property var previousMainApp: null
408 property var previousSideApp: null
410 onShownChanged: {
411 if (shown) {
412 lockscreen.reset();
413 // If there is only one user, we start authenticating with that one here.
414 // If there are more users, the Greeter will handle that
415 if (LightDM.Users.count == 1) {
416 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
417 }
418 greeter.forceActiveFocus();
419 // FIXME: *FocusedApplication are not updated when unfocused, hence the need to check whether
420 // the stage was actually shown
421 if (mainStage.fullyShown) greeter.previousMainApp = applicationManager.mainStageFocusedApplication;
422 if (sideStage.fullyShown) greeter.previousSideApp = applicationManager.sideStageFocusedApplication;
423 applicationManager.unfocusCurrentApplication();
424 } else {
425 if (greeter.previousMainApp) {
426 applicationManager.focusApplication(greeter.previousMainApp);
427 greeter.previousMainApp = null;
428 }
429 if (greeter.previousSideApp) {
430 applicationManager.focusApplication(greeter.previousSideApp);
431 greeter.previousSideApp = null;
432 }
433 }
434 }
436 onUnlocked: greeter.hide()
437 onSelected: {
438 var bgPath = greeter.model.data(uid, LightDM.UserRoles.BackgroundPathRole)
439 shell.background = bgPath ? bgPath : default_background
440 }
442 onLeftTeaserPressedChanged: {
443 if (leftTeaserPressed) {
444 launcher.tease();
445 }
446 }
447 }
449 InputFilterArea {
450 anchors.fill: parent
451 blockInput: greeter.shown || lockscreen.shown
452 }
454 Item {
455 id: overlay
457 anchors.fill: parent
459 Panel {
460 id: panel
461 anchors.fill: parent //because this draws indicator menus
462 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
463 indicators {
464 hides: [launcher]
465 }
466 fullscreenMode: shell.fullscreenMode
467 searchVisible: !greeter.shown && !lockscreen.shown
469 InputFilterArea {
470 anchors.fill: parent
471 blockInput: panel.indicators.shown
472 }
473 }
475 Hud {
476 id: hud
478 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
479 height: parent.height
481 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown
482 shown: false
483 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
484 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
486 Connections {
487 target: shell.applicationManager
488 onMainStageFocusedApplicationChanged: hud.hide()
489 onSideStageFocusedApplicationChanged: hud.hide()
490 }
492 InputFilterArea {
493 anchors.fill: parent
494 blockInput: hud.shown
495 }
496 }
498 Revealer {
499 id: hudRevealer
501 enabled: hud.shown
502 width: hud.width
503 anchors.left: hud.left
504 height: parent.height
505 target: hud.revealerTarget
506 closedValue: height
507 openedValue: 0
508 direction: Qt.RightToLeft
509 orientation: Qt.Vertical
510 handleSize: hud.handleHeight
511 onCloseClicked: target.hide()
512 }
514 Bottombar {
515 theHud: hud
516 anchors.fill: parent
517 enabled: !panel.indicators.shown
518 applicationIsOnForeground: applicationManager.mainStageFocusedApplication
519 || applicationManager.sideStageFocusedApplication
520 }
522 InputFilterArea {
523 blockInput: launcher.shown
524 anchors {
525 top: parent.top
526 bottom: parent.bottom
527 left: parent.left
528 }
529 width: launcher.width
530 }
532 Launcher {
533 id: launcher
535 anchors.top: parent.top
536 anchors.bottom: parent.bottom
537 width: parent.width
538 dragAreaWidth: shell.edgeSize
539 available: !greeter.locked
540 onDashItemSelected: {
541 greeter.hide()
542 // Animate if moving between application and dash
543 if (!stages.shown) {
544 dash.setCurrentScope("home.scope", true, false)
545 } else {
546 dash.setCurrentScope("home.scope", false, false)
547 }
548 stages.hide();
549 }
550 onDash: {
551 if (stages.shown) {
552 dash.setCurrentScope("applications.scope", true, false)
553 stages.hide();
554 launcher.hide();
555 }
556 }
557 onLauncherApplicationSelected:{
558 greeter.hide()
559 shell.activateApplication(desktopFile)
560 }
561 onShownChanged: {
562 if (shown) {
563 panel.indicators.hide()
564 hud.hide()
565 }
566 }
567 }
569 Notifications {
570 id: notifications
572 model: NotificationBackend.Model
573 anchors {
574 top: parent.top
575 right: parent.right
576 bottom: parent.bottom
577 leftMargin: units.gu(1)
578 rightMargin: units.gu(1)
579 topMargin: panel.panelHeight + units.gu(1)
580 }
581 states: [
582 State {
583 name: "narrow"
584 when: overlay.width <= units.gu(60)
585 AnchorChanges { target: notifications; anchors.left: parent.left }
586 },
587 State {
588 name: "wide"
589 when: overlay.width > units.gu(60)
590 AnchorChanges { target: notifications; anchors.left: undefined }
591 PropertyChanges { target: notifications; width: units.gu(38) }
592 }
593 ]
594 }
595 }
597 focus: true
599 InputFilterArea {
600 anchors {
601 top: parent.top
602 bottom: parent.bottom
603 left: parent.left
604 }
605 width: shell.edgeSize
606 blockInput: true
607 }
609 InputFilterArea {
610 anchors {
611 top: parent.top
612 bottom: parent.bottom
613 right: parent.right
614 }
615 width: shell.edgeSize
616 blockInput: true
617 }
619 Binding {
620 target: i18n
621 property: "domain"
622 value: "unity8"
623 }
625 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
626 MouseArea {
627 anchors.bottom: parent.bottom
628 anchors.left: parent.left
629 anchors.right: parent.right
630 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
632 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
633 }