Unity 8
 All Classes Functions Properties
Shell.qml
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  */
16 
17 import QtQuick 2.0
18 import AccountsService 0.1
19 import GSettings 1.0
20 import Unity.Application 0.1
21 import Ubuntu.Components 0.1
22 import Ubuntu.Gestures 0.1
23 import Unity.Launcher 0.1
24 import LightDM 0.1 as LightDM
25 import Powerd 0.1
26 import SessionBroadcast 0.1
27 import "Dash"
28 import "Greeter"
29 import "Launcher"
30 import "Panel"
31 import "Components"
32 import "Notifications"
33 import Unity.Notifications 1.0 as NotificationBackend
34 
35 FocusScope {
36  id: shell
37 
38  // this is only here to select the width / height of the window if not running fullscreen
39  property bool tablet: false
40  width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
41  height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
42 
43  property real edgeSize: units.gu(2)
44  property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
45  property url background
46  readonly property real panelHeight: panel.panelHeight
47 
48  property bool dashShown: dash.shown
49 
50  property bool sideStageEnabled: shell.width >= units.gu(100)
51  readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
52 
53  function activateApplication(appId) {
54  if (ApplicationManager.findApplication(appId)) {
55  ApplicationManager.requestFocusApplication(appId);
56  stages.show(true);
57  if (stages.locked && ApplicationManager.focusedApplicationId == appId) {
58  applicationsDisplayLoader.item.select(appId);
59  }
60  } else {
61  var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
62  ApplicationManager.startApplication(appId, execFlags);
63  stages.show(false);
64  }
65  }
66 
67  Binding {
68  target: LauncherModel
69  property: "applicationManager"
70  value: ApplicationManager
71  }
72 
73  Component.onCompleted: {
74  Theme.name = "Ubuntu.Components.Themes.SuruGradient"
75  }
76 
77  GSettings {
78  id: backgroundSettings
79  schema.id: "org.gnome.desktop.background"
80  }
81  property url gSettingsPicture: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : shell.defaultBackground
82  onGSettingsPictureChanged: {
83  shell.background = gSettingsPicture
84  }
85 
86  VolumeControl {
87  id: volumeControl
88  }
89 
90  Keys.onVolumeUpPressed: volumeControl.volumeUp()
91  Keys.onVolumeDownPressed: volumeControl.volumeDown()
92 
93  Item {
94  id: underlayClipper
95  anchors.fill: parent
96  anchors.rightMargin: stages.overlayWidth
97  clip: stages.overlayMode && !stages.painting
98 
99  InputFilterArea {
100  anchors.fill: parent
101  blockInput: parent.clip
102  }
103 
104  Item {
105  id: underlay
106  objectName: "underlay"
107  anchors.fill: parent
108  anchors.rightMargin: -parent.anchors.rightMargin
109 
110  // Whether the underlay is fully covered by opaque UI elements.
111  property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
112 
113  // Whether the user should see the topmost application surface (if there's one at all).
114  readonly property bool applicationSurfaceShouldBeSeen: stages.shown && !stages.painting && !stages.overlayMode
115 
116  // NB! Application surfaces are stacked behind the shell one. So they can only be seen by the user
117  // through the translucent parts of the shell surface.
118  visible: !fullyCovered && !applicationSurfaceShouldBeSeen
119 
120  CrossFadeImage {
121  id: backgroundImage
122  objectName: "backgroundImage"
123 
124  anchors.fill: parent
125  source: shell.background
126  fillMode: Image.PreserveAspectCrop
127  }
128 
129  Rectangle {
130  anchors.fill: parent
131  color: "black"
132  opacity: dash.disappearingAnimationProgress
133  }
134 
135  Image {
136  anchors.fill: dash
137  source: shell.width > shell.height ? "Dash/graphics/paper_landscape.png" : "Dash/graphics/paper_portrait.png"
138  fillMode: Image.PreserveAspectCrop
139  horizontalAlignment: Image.AlignRight
140  verticalAlignment: Image.AlignTop
141  }
142 
143  Dash {
144  id: dash
145  objectName: "dash"
146 
147  available: !greeter.shown && !lockscreen.shown
148  hides: [stages, launcher, panel.indicators]
149  shown: disappearingAnimationProgress !== 1.0 && greeterWrapper.showProgress !== 1.0
150  enabled: disappearingAnimationProgress === 0.0 && greeterWrapper.showProgress === 0.0 && edgeDemo.dashEnabled
151 
152  anchors {
153  fill: parent
154  topMargin: panel.panelHeight
155  }
156 
157  contentScale: 1.0 - 0.2 * disappearingAnimationProgress
158  opacity: 1.0 - disappearingAnimationProgress
159  property real disappearingAnimationProgress: {
160  if (stages.overlayMode) {
161  return 0;
162  } else {
163  return stages.showProgress
164  }
165  }
166 
167  // FIXME: only necessary because stages.showProgress is not animated
168  Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
169  }
170  }
171  }
172 
173  EdgeDragArea {
174  id: stagesDragHandle
175  direction: Direction.Leftwards
176 
177  anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
178  width: shell.edgeSize
179 
180  property real progress: stages.width
181 
182  onTouchXChanged: {
183  if (status == DirectionalDragArea.Recognized) {
184  if (ApplicationManager.count == 0) {
185  progress = Math.max(stages.width - stagesDragHandle.width + touchX, stages.width * .3)
186  } else {
187  progress = stages.width - stagesDragHandle.width + touchX
188  }
189  }
190  }
191 
192  onDraggingChanged: {
193  if (!dragging) {
194  if (ApplicationManager.count > 0 && progress < stages.width - units.gu(10)) {
195  stages.show(true)
196  }
197  stagesDragHandle.progress = stages.width;
198  }
199  }
200  }
201 
202  Item {
203  id: stages
204  objectName: "stages"
205  width: parent.width
206  height: parent.height
207 
208  x: {
209  if (shown) {
210  if (overlayMode || locked) {
211  return 0;
212  }
213  return launcher.progress
214  } else {
215  return stagesDragHandle.progress
216  }
217  }
218 
219  Behavior on x { SmoothedAnimation { velocity: 600; duration: UbuntuAnimation.FastDuration } }
220 
221  property bool shown: false
222 
223  property real showProgress: overlayMode ? 0 : MathUtils.clamp(1 - x / shell.width, 0, 1)
224 
225  property bool fullyShown: x == 0
226  property bool fullyHidden: x == width
227 
228  property bool painting: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.painting : false
229  property bool fullscreen: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.fullscreen : false
230  property bool overlayMode: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayMode : false
231  property int overlayWidth: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayWidth : false
232  property bool locked: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.locked : false
233 
234  function show(focusApp) {
235  shown = true;
236  panel.indicators.hide();
237  edgeDemo.stopDemo();
238  greeter.hide();
239  if (!ApplicationManager.focusedApplicationId && ApplicationManager.count > 0 && focusApp) {
240  ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
241  }
242  }
243 
244  function hide() {
245  shown = false;
246  if (ApplicationManager.focusedApplicationId) {
247  ApplicationManager.unfocusCurrentApplication();
248  }
249  }
250 
251  Connections {
252  target: ApplicationManager
253 
254  onFocusRequested: {
255  stages.show(true);
256  }
257 
258  onFocusedApplicationIdChanged: {
259  if (ApplicationManager.focusedApplicationId.length > 0) {
260  stages.show(false);
261  } else {
262  if (!stages.overlayMode) {
263  stages.hide();
264  }
265  }
266  }
267 
268  onApplicationAdded: {
269  stages.show(false);
270  }
271 
272  onApplicationRemoved: {
273  if (ApplicationManager.focusedApplicationId.length == 0) {
274  stages.hide();
275  }
276  }
277  }
278 
279  Loader {
280  id: applicationsDisplayLoader
281  anchors.fill: parent
282 
283  source: shell.sideStageEnabled ? "Stages/StageWithSideStage.qml" : "Stages/PhoneStage.qml"
284 
285  Binding {
286  target: applicationsDisplayLoader.item
287  property: "moving"
288  value: !stages.fullyShown
289  }
290  Binding {
291  target: applicationsDisplayLoader.item
292  property: "shown"
293  value: stages.shown
294  }
295  Binding {
296  target: applicationsDisplayLoader.item
297  property: "dragAreaWidth"
298  value: shell.edgeSize
299  }
300  }
301  }
302 
303  Lockscreen {
304  id: lockscreen
305  objectName: "lockscreen"
306 
307  readonly property int backgroundTopMargin: -panel.panelHeight
308 
309  hides: [launcher, panel.indicators]
310  shown: false
311  enabled: true
312  showAnimation: StandardAnimation { property: "opacity"; to: 1 }
313  hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
314  y: panel.panelHeight
315  x: required ? 0 : - width
316  width: parent.width
317  height: parent.height - panel.panelHeight
318  background: shell.background
319  pinLength: 4
320 
321  onEntered: LightDM.Greeter.respond(passphrase);
322  onCancel: greeter.show()
323 
324  Component.onCompleted: {
325  if (LightDM.Users.count == 1) {
326  LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
327  }
328  }
329  }
330 
331  Connections {
332  target: LightDM.Greeter
333 
334  onShowPrompt: {
335  if (LightDM.Users.count == 1) {
336  // TODO: There's no better way for now to determine if its a PIN or a passphrase.
337  if (text == "PIN") {
338  lockscreen.alphaNumeric = false
339  } else {
340  lockscreen.alphaNumeric = true
341  }
342  lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
343  lockscreen.show();
344  }
345  }
346 
347  onAuthenticationComplete: {
348  if (LightDM.Greeter.promptless) {
349  return;
350  }
351  if (LightDM.Greeter.authenticated) {
352  lockscreen.hide();
353  } else {
354  lockscreen.clear(true);
355  }
356  }
357  }
358 
359  Rectangle {
360  anchors.fill: parent
361  color: "black"
362  opacity: greeterWrapper.showProgress * 0.8
363  }
364 
365  Item {
366  // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
367  id: greeterWrapper
368  x: launcher.progress
369  y: panel.panelHeight
370  width: parent.width
371  height: parent.height - panel.panelHeight
372 
373  Behavior on x {SmoothedAnimation{velocity: 600}}
374 
375  readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
376 
377  Greeter {
378  id: greeter
379  objectName: "greeter"
380 
381  available: true
382  hides: [launcher, panel.indicators]
383  shown: true
384 
385  defaultBackground: shell.background
386 
387  width: parent.width
388  height: parent.height
389 
390  dragHandleWidth: shell.edgeSize
391 
392  onShownChanged: {
393  if (shown) {
394  lockscreen.reset();
395  // If there is only one user, we start authenticating with that one here.
396  // If there are more users, the Greeter will handle that
397  if (LightDM.Users.count == 1) {
398  LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
399  }
400  greeter.forceActiveFocus();
401  }
402  }
403 
404  onUnlocked: greeter.hide()
405  onSelected: {
406  // Update launcher items for new user
407  var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
408  AccountsService.user = user;
409  LauncherModel.setUser(user);
410  }
411 
412  onTease: launcher.tease()
413 
414  Binding {
415  target: ApplicationManager
416  property: "suspended"
417  value: greeter.shown && greeterWrapper.showProgress == 1
418  }
419  }
420  }
421 
422  InputFilterArea {
423  anchors.fill: parent
424  blockInput: ApplicationManager.focusedApplicationId.length === 0 || greeter.shown || lockscreen.shown || launcher.shown
425  || panel.indicators.shown
426  }
427 
428  Connections {
429  id: powerConnection
430  target: Powerd
431 
432  onDisplayPowerStateChange: {
433  // We ignore any display-off signals when the proximity sensor
434  // is active. This usually indicates something like a phone call.
435  if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
436  greeter.showNow();
437  }
438 
439  // No reason to chew demo CPU when user isn't watching
440  if (status == Powerd.Off) {
441  edgeDemo.paused = true;
442  } else if (status == Powerd.On) {
443  edgeDemo.paused = false;
444  }
445  }
446  }
447 
448  function showHome() {
449  var animate = !greeter.shown && !stages.shown
450  greeter.hide()
451  dash.setCurrentScope("clickscope", animate, false)
452  stages.hide()
453  }
454 
455  function hideIndicatorMenu(delay) {
456  panel.hideIndicatorMenu(delay);
457  }
458 
459  Item {
460  id: overlay
461 
462  anchors.fill: parent
463 
464  Panel {
465  id: panel
466  anchors.fill: parent //because this draws indicator menus
467  indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
468  indicators {
469  hides: [launcher]
470  available: edgeDemo.panelEnabled
471  contentEnabled: edgeDemo.panelContentEnabled
472  }
473  property string focusedAppId: ApplicationManager.focusedApplicationId
474  property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
475  fullscreenMode: focusedApplication && stages.fullscreen && !greeter.shown && !lockscreen.shown
476  searchVisible: !greeter.shown && !lockscreen.shown && dash.shown && dash.searchable
477 
478  InputFilterArea {
479  anchors {
480  top: parent.top
481  left: parent.left
482  right: parent.right
483  }
484  height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
485  blockInput: true
486  }
487  }
488 
489  InputFilterArea {
490  blockInput: launcher.shown
491  anchors {
492  top: parent.top
493  bottom: parent.bottom
494  left: parent.left
495  }
496  width: launcher.width
497  }
498 
499  Launcher {
500  id: launcher
501 
502  readonly property bool dashSwipe: progress > 0
503 
504  anchors.top: parent.top
505  anchors.bottom: parent.bottom
506  width: parent.width
507  dragAreaWidth: shell.edgeSize
508  available: edgeDemo.launcherEnabled
509 
510  onShowDashHome: {
511  if (edgeDemo.running)
512  return;
513 
514  showHome()
515  }
516  onDash: {
517  if (stages.shown && !stages.overlayMode) {
518  if (!stages.locked) {
519  stages.hide();
520  launcher.hide();
521  }
522  }
523  if (greeter.shown) {
524  greeter.hideRight();
525  launcher.hide();
526  }
527  }
528  onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
529  onLauncherApplicationSelected: {
530  if (!edgeDemo.running)
531  shell.activateApplication(appId)
532  }
533  onShownChanged: {
534  if (shown) {
535  panel.indicators.hide()
536  }
537  }
538  }
539 
540  Rectangle {
541  id: modalNotificationBackground
542 
543  visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
544  color: "#000000"
545  anchors.fill: parent
546  opacity: 0.5
547 
548  MouseArea {
549  anchors.fill: parent
550  }
551 
552  InputFilterArea {
553  anchors.fill: parent
554  blockInput: modalNotificationBackground.visible
555  }
556  }
557 
558  Notifications {
559  id: notifications
560 
561  model: NotificationBackend.Model
562  margin: units.gu(1)
563 
564  anchors {
565  top: parent.top
566  right: parent.right
567  bottom: parent.bottom
568  topMargin: panel.panelHeight
569  }
570  states: [
571  State {
572  name: "narrow"
573  when: overlay.width <= units.gu(60)
574  AnchorChanges { target: notifications; anchors.left: parent.left }
575  },
576  State {
577  name: "wide"
578  when: overlay.width > units.gu(60)
579  AnchorChanges { target: notifications; anchors.left: undefined }
580  PropertyChanges { target: notifications; width: units.gu(38) }
581  }
582  ]
583 
584  InputFilterArea {
585  anchors { left: parent.left; right: parent.right }
586  height: parent.contentHeight
587  blockInput: height > 0
588  }
589  }
590  }
591 
592  focus: true
593  onFocusChanged: if (!focus) forceActiveFocus();
594 
595  InputFilterArea {
596  anchors {
597  top: parent.top
598  bottom: parent.bottom
599  left: parent.left
600  }
601  width: shell.edgeSize
602  blockInput: true
603  }
604 
605  InputFilterArea {
606  anchors {
607  top: parent.top
608  bottom: parent.bottom
609  right: parent.right
610  }
611  width: shell.edgeSize
612  blockInput: true
613  }
614 
615  Binding {
616  target: i18n
617  property: "domain"
618  value: "unity8"
619  }
620 
621  OSKController {
622  anchors.topMargin: panel.panelHeight
623  anchors.fill: parent // as needs to know the geometry of the shell
624  }
625 
626  //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
627  MouseArea {
628  anchors.bottom: parent.bottom
629  anchors.left: parent.left
630  anchors.right: parent.right
631  height: ApplicationManager.keyboardVisible ? ApplicationManager.keyboardHeight : 0
632 
633  enabled: ApplicationManager.keyboardVisible
634  }
635 
636  Label {
637  anchors.centerIn: parent
638  visible: ApplicationManager.fake ? ApplicationManager.fake : false
639  text: "EARLY ALPHA\nNOT READY FOR USE"
640  color: "lightgrey"
641  opacity: 0.2
642  font.weight: Font.Black
643  horizontalAlignment: Text.AlignHCenter
644  verticalAlignment: Text.AlignVCenter
645  fontSizeMode: Text.Fit
646  rotation: -45
647  scale: Math.min(parent.width, parent.height) / width
648  }
649 
650  EdgeDemo {
651  id: edgeDemo
652  greeter: greeter
653  launcher: launcher
654  dash: dash
655  indicators: panel.indicators
656  underlay: underlay
657  }
658 
659  Connections {
660  target: SessionBroadcast
661  onShowHome: showHome()
662  }
663 }