Unity 8
Panel.qml
1 /*
2  * Copyright (C) 2013-2017 Canonical, Ltd.
3  * Copyright (C) 2020 UBports Foundation
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; version 3.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 import QtQuick 2.12
19 import Ubuntu.Components 1.3
20 import Ubuntu.Layouts 1.0
21 import Unity.Application 0.1
22 import Unity.Indicators 0.1
23 import Utils 0.1
24 import Unity.ApplicationMenu 0.1
25 
26 import QtQuick.Window 2.2
27 
28 import "../ApplicationMenus"
29 import "../Components"
30 import "../Components/PanelState"
31 import ".."
32 import "Indicators"
33 
34 Item {
35  id: root
36  readonly property real panelHeight: panelArea.y + minimizedPanelHeight
37  readonly property bool fullyClosed: indicators.fullyClosed && applicationMenus.fullyClosed
38 
39  property real minimizedPanelHeight: units.gu(3)
40  property real expandedPanelHeight: units.gu(7)
41  property real menuWidth: partialWidth ? units.gu(40) : width
42  property alias applicationMenuContentX: __applicationMenus.menuContentX
43 
44  property alias applicationMenus: __applicationMenus
45  property alias indicators: __indicators
46  property bool fullscreenMode: false
47  property real panelAreaShowProgress: 1.0
48  property bool greeterShown: false
49  property bool hasKeyboard: false
50  property bool supportsMultiColorLed: true
51 
52  // Whether our expanded menus should take up the full width of the panel
53  property bool partialWidth: width >= units.gu(60)
54 
55  property string mode: "staged"
56 
57  MouseArea {
58  id: backMouseEater
59  anchors.fill: parent
60  anchors.topMargin: panelHeight
61  visible: !indicators.fullyClosed || !applicationMenus.fullyClosed
62  enabled: visible
63  hoverEnabled: true // should also eat hover events, otherwise they will pass through
64 
65  onClicked: {
66  __applicationMenus.hide();
67  __indicators.hide();
68  }
69  }
70 
71  Binding {
72  target: PanelState
73  property: "panelHeight"
74  value: minimizedPanelHeight
75  }
76 
77  RegisteredApplicationMenuModel {
78  id: registeredMenuModel
79  persistentSurfaceId: PanelState.focusedPersistentSurfaceId
80  }
81 
82  QtObject {
83  id: d
84 
85  property bool revealControls: !greeterShown &&
86  !applicationMenus.shown &&
87  !indicators.shown &&
88  (decorationMouseArea.containsMouse || menuBarLoader.menusRequested)
89 
90  property bool showWindowDecorationControls: (revealControls && PanelState.decorationsVisible) ||
91  PanelState.decorationsAlwaysVisible
92 
93  property bool showPointerMenu: revealControls && enablePointerMenu &&
94  (PanelState.decorationsVisible || mode == "windowed")
95 
96  property bool enablePointerMenu: applicationMenus.available &&
97  applicationMenus.model
98 
99  property bool showTouchMenu: !greeterShown &&
100  !showPointerMenu &&
101  !showWindowDecorationControls
102 
103  property bool enableTouchMenus: showTouchMenu &&
104  applicationMenus.available &&
105  applicationMenus.model
106  }
107 
108  Item {
109  id: panelArea
110  objectName: "panelArea"
111 
112  anchors.fill: parent
113 
114  transform: Translate {
115  y: indicators.state === "initial"
116  ? (1.0 - panelAreaShowProgress) * - minimizedPanelHeight
117  : 0
118  }
119 
120  BorderImage {
121  id: indicatorsDropShadow
122  anchors {
123  fill: __indicators
124  margins: -units.gu(1)
125  }
126  visible: !__indicators.fullyClosed
127  source: "graphics/rectangular_dropshadow.sci"
128  }
129 
130  BorderImage {
131  id: appmenuDropShadow
132  anchors {
133  fill: __applicationMenus
134  margins: -units.gu(1)
135  }
136  visible: !__applicationMenus.fullyClosed
137  source: "graphics/rectangular_dropshadow.sci"
138  }
139 
140  BorderImage {
141  id: panelDropShadow
142  anchors {
143  fill: panelAreaBackground
144  bottomMargin: -units.gu(1)
145  }
146  visible: PanelState.dropShadow
147  source: "graphics/rectangular_dropshadow.sci"
148  }
149 
150  Rectangle {
151  id: panelAreaBackground
152  color: callHint.visible ? theme.palette.normal.activity : theme.palette.normal.background
153  anchors {
154  top: parent.top
155  left: parent.left
156  right: parent.right
157  }
158  height: minimizedPanelHeight
159 
160  Behavior on color { ColorAnimation { duration: UbuntuAnimation.FastDuration } }
161  }
162 
163  MouseArea {
164  id: decorationMouseArea
165  objectName: "windowControlArea"
166  anchors {
167  left: parent.left
168  right: parent.right
169  }
170  height: minimizedPanelHeight
171  hoverEnabled: !__indicators.shown
172  onClicked: {
173  if (callHint.visible) {
174  callHint.showLiveCall();
175  }
176  }
177 
178  onPressed: {
179  if (!callHint.visible) {
180  // let it fall through to the window decoration of the maximized window behind, if any
181  mouse.accepted = false;
182  }
183  var menubar = menuBarLoader.item;
184  if (menubar) {
185  menubar.invokeMenu(mouse);
186  }
187  }
188 
189  Row {
190  anchors.fill: parent
191  spacing: units.gu(2)
192 
193  // WindowControlButtons inside the mouse area, otherwise QML doesn't grok nested hover events :/
194  // cf. https://bugreports.qt.io/browse/QTBUG-32909
195  WindowControlButtons {
196  id: windowControlButtons
197  objectName: "panelWindowControlButtons"
198  height: indicators.minimizedPanelHeight
199  opacity: d.showWindowDecorationControls ? 1 : 0
200  visible: opacity != 0
201  Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
202 
203  active: PanelState.decorationsVisible || PanelState.decorationsAlwaysVisible
204  windowIsMaximized: true
205  onCloseClicked: PanelState.closeClicked()
206  onMinimizeClicked: PanelState.minimizeClicked()
207  onMaximizeClicked: PanelState.restoreClicked()
208  closeButtonShown: PanelState.closeButtonShown
209  }
210 
211  Loader {
212  id: menuBarLoader
213  objectName: "menuBarLoader"
214  height: parent.height
215  enabled: d.enablePointerMenu
216  opacity: d.showPointerMenu ? 1 : 0
217  visible: opacity != 0
218  Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
219  active: d.showPointerMenu && !callHint.visible
220 
221  width: parent.width - windowControlButtons.width - units.gu(2) - __indicators.barWidth
222 
223  readonly property bool menusRequested: menuBarLoader.item ? menuBarLoader.item.showRequested : false
224 
225  sourceComponent: MenuBar {
226  id: bar
227  objectName: "menuBar"
228  anchors.left: parent ? parent.left : undefined
229  anchors.margins: units.gu(1)
230  height: menuBarLoader.height
231  enableKeyFilter: valid && PanelState.decorationsVisible
232  unityMenuModel: __applicationMenus.model
233 
234  Connections {
235  target: __applicationMenus
236  onShownChanged: bar.dismiss();
237  }
238 
239  Connections {
240  target: __indicators
241  onShownChanged: bar.dismiss();
242  }
243 
244  onDoubleClicked: PanelState.restoreClicked()
245  onPressed: mouse.accepted = false // let the parent mouse area handle this, so it can both unsnap window and show menu
246  }
247  }
248  }
249 
250  ActiveCallHint {
251  id: callHint
252  objectName: "callHint"
253 
254  anchors.centerIn: parent
255  height: minimizedPanelHeight
256 
257  visible: active && indicators.state == "initial" && __applicationMenus.state == "initial"
258  greeterShown: root.greeterShown
259  }
260  }
261 
262  PanelMenu {
263  id: __applicationMenus
264 
265  x: menuContentX
266  model: registeredMenuModel.model
267  width: root.menuWidth
268  overFlowWidth: width
269  minimizedPanelHeight: root.minimizedPanelHeight
270  expandedPanelHeight: root.expandedPanelHeight
271  openedHeight: root.height
272  alignment: Qt.AlignLeft
273  enableHint: !callHint.active && !fullscreenMode
274  showOnClick: false
275  panelColor: panelAreaBackground.color
276 
277  onShowTapped: {
278  if (callHint.active) {
279  callHint.showLiveCall();
280  }
281  }
282 
283  hideRow: !expanded
284  rowItemDelegate: ActionItem {
285  id: actionItem
286  property int ownIndex: index
287  objectName: "appMenuItem"+index
288  enabled: model.sensitive
289 
290  width: _title.width + units.gu(2)
291  height: parent.height
292 
293  action: Action {
294  text: model.label.replace("_", "&")
295  }
296 
297  Label {
298  id: _title
299  anchors.centerIn: parent
300  text: actionItem.text
301  horizontalAlignment: Text.AlignLeft
302  color: enabled ? theme.palette.normal.backgroundText : theme.palette.disabled.backgroundText
303  }
304  }
305 
306  pageDelegate: PanelMenuPage {
307  readonly property bool isCurrent: modelIndex == __applicationMenus.currentMenuIndex
308  onIsCurrentChanged: {
309  if (isCurrent && menuModel) {
310  menuModel.aboutToShow(modelIndex);
311  }
312  }
313 
314  menuModel: __applicationMenus.model
315  submenuIndex: modelIndex
316 
317  factory: ApplicationMenuItemFactory {
318  rootModel: __applicationMenus.model
319  }
320  }
321 
322  enabled: d.enableTouchMenus
323  opacity: d.showTouchMenu ? 1 : 0
324  visible: opacity != 0
325  Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
326 
327  onEnabledChanged: {
328  if (!enabled) hide();
329  }
330  }
331 
332  Item {
333  id: panelTitleHolder
334  anchors {
335  left: parent.left
336  leftMargin: units.gu(1)
337  right: __indicators.left
338  rightMargin: units.gu(1)
339  }
340  height: root.minimizedPanelHeight
341 
342  Label {
343  id: rowLabel
344  anchors {
345  left: parent.left
346  right: root.partialWidth ? parent.right : parent.left
347  rightMargin: touchMenuIcon.width
348  }
349  objectName: "panelTitle"
350  height: root.minimizedPanelHeight
351  verticalAlignment: Text.AlignVCenter
352  elide: Text.ElideRight
353  maximumLineCount: 1
354  fontSize: "medium"
355  font.weight: Font.Medium
356  color: theme.palette.selected.backgroundText
357  text: (root.partialWidth && !callHint.visible) ? PanelState.title : ""
358  opacity: __applicationMenus.visible && !__applicationMenus.expanded
359  Behavior on opacity { NumberAnimation { duration: UbuntuAnimation.SnapDuration } }
360  visible: opacity !== 0
361  }
362 
363  Icon {
364  id: touchMenuIcon
365  objectName: "touchMenuIcon"
366  anchors {
367  left: parent.left
368  leftMargin: rowLabel.contentWidth + units.dp(2)
369  verticalCenter: parent.verticalCenter
370  }
371  width: units.gu(2)
372  height: units.gu(2)
373  name: "down"
374  color: theme.palette.normal.backgroundText
375  opacity: !__applicationMenus.expanded && d.enableTouchMenus && !callHint.visible
376  Behavior on opacity { NumberAnimation { duration: UbuntuAnimation.SnapDuration } }
377  visible: opacity !== 0
378  }
379  }
380 
381  PanelMenu {
382  id: __indicators
383  objectName: "indicators"
384 
385  anchors {
386  top: parent.top
387  right: parent.right
388  }
389  width: root.menuWidth
390  minimizedPanelHeight: root.minimizedPanelHeight
391  expandedPanelHeight: root.expandedPanelHeight
392  openedHeight: root.height
393 
394  overFlowWidth: width - appMenuClear
395  enableHint: !callHint.active && !fullscreenMode
396  showOnClick: !callHint.visible
397  panelColor: panelAreaBackground.color
398 
399  // On small screens, the Indicators' handle area is the entire top
400  // bar unless there is an application menu. In that case, our handle
401  // needs to allow for some room to clear the application menu.
402  property var appMenuClear: (d.enableTouchMenus && !partialWidth) ? units.gu(7) : 0
403 
404  onShowTapped: {
405  if (callHint.active) {
406  callHint.showLiveCall();
407  }
408  }
409 
410  rowItemDelegate: IndicatorItem {
411  id: indicatorItem
412  objectName: identifier+"-panelItem"
413 
414  property int ownIndex: index
415  readonly property bool overflow: parent.width - (x - __indicators.rowContentX) > __indicators.overFlowWidth
416  readonly property bool hidden: !expanded && (overflow || !indicatorVisible || hideSessionIndicator || hideKeyboardIndicator)
417  // HACK for indicator-session
418  readonly property bool hideSessionIndicator: identifier == "indicator-session" && Math.min(Screen.width, Screen.height) <= units.gu(60)
419  // HACK for indicator-keyboard
420  readonly property bool hideKeyboardIndicator: identifier == "indicator-keyboard" && !hasKeyboard
421 
422  height: parent.height
423  expanded: indicators.expanded
424  selected: ListView.isCurrentItem
425 
426  identifier: model.identifier
427  busName: indicatorProperties.busName
428  actionsObjectPath: indicatorProperties.actionsObjectPath
429  menuObjectPath: indicatorProperties.menuObjectPath
430 
431  opacity: hidden ? 0.0 : 1.0
432  Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
433 
434  width: ((expanded || indicatorVisible) && !hideSessionIndicator && !hideKeyboardIndicator) ? implicitWidth : 0
435 
436  Behavior on width { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
437  }
438 
439  pageDelegate: PanelMenuPage {
440  objectName: modelData.identifier + "-page"
441  submenuIndex: 0
442 
443  menuModel: delegate.menuModel
444 
445  factory: IndicatorMenuItemFactory {
446  indicator: {
447  var context = modelData.identifier;
448  if (context && context.indexOf("fake-") === 0) {
449  context = context.substring("fake-".length)
450  }
451  return context;
452  }
453  rootModel: delegate.menuModel
454  }
455 
456  IndicatorDelegate {
457  id: delegate
458  busName: modelData.indicatorProperties.busName
459  actionsObjectPath: modelData.indicatorProperties.actionsObjectPath
460  menuObjectPath: modelData.indicatorProperties.menuObjectPath
461  }
462  }
463 
464  enabled: !applicationMenus.expanded
465  opacity: !callHint.visible && !applicationMenus.expanded ? 1 : 0
466  Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
467 
468  onEnabledChanged: {
469  if (!enabled) hide();
470  }
471  }
472  }
473 
474  IndicatorsLight {
475  id: indicatorLights
476  supportsMultiColorLed: root.supportsMultiColorLed
477  }
478 
479  states: [
480  State {
481  name: "onscreen" //fully opaque and visible at top edge of screen
482  when: !fullscreenMode
483  PropertyChanges {
484  target: panelArea;
485  anchors.topMargin: 0
486  opacity: 1;
487  }
488  },
489  State {
490  name: "offscreen" //pushed off screen
491  when: fullscreenMode
492  PropertyChanges {
493  target: panelArea;
494  anchors.topMargin: {
495  if (indicators.state !== "initial") return 0;
496  if (applicationMenus.state !== "initial") return 0;
497  return -minimizedPanelHeight;
498  }
499  opacity: indicators.fullyClosed && applicationMenus.fullyClosed ? 0.0 : 1.0
500  }
501  PropertyChanges {
502  target: indicators.showDragHandle;
503  anchors.bottomMargin: -units.gu(1)
504  }
505  PropertyChanges {
506  target: applicationMenus.showDragHandle;
507  anchors.bottomMargin: -units.gu(1)
508  }
509  }
510  ]
511 
512  transitions: [
513  Transition {
514  to: "onscreen"
515  UbuntuNumberAnimation { target: panelArea; properties: "anchors.topMargin,opacity" }
516  },
517  Transition {
518  to: "offscreen"
519  UbuntuNumberAnimation { target: panelArea; properties: "anchors.topMargin,opacity" }
520  }
521  ]
522 }