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