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