Lomiri
Loading...
Searching...
No Matches
Panel.qml
1/*
2 * Copyright (C) 2013-2017 Canonical Ltd.
3 * Copyright (C) 2020-2026 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 "../../.."
32import "../../Indicators"
33import "../.."
34
35Item {
36 id: root
37
38 readonly property real panelHeight: panelArea.y + minimizedPanelHeight
39 readonly property bool fullyClosed: indicators.fullyClosed && applicationMenus.fullyClosed
40
41 property real minimizedPanelHeight: units.gu(3)
42 property real expandedPanelHeight: units.gu(7)
43 property real menuWidth: partialWidth ? units.gu(40) : width
44 property alias applicationMenuContentX: __applicationMenus.menuContentX
45 property int screenIndex: -1
46 property var orientation: Qt.PrimaryOrientation
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 var 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 screenIndex: root.screenIndex
307
308 onShowTapped: {
309 if (callHint.active) {
310 callHint.showLiveCall();
311 }
312 }
313
314 hideRow: !expanded
315 rowItemDelegate: ActionItem {
316 id: actionItem
317 property int ownIndex: index
318 objectName: "appMenuItem"+index
319 enabled: model.sensitive
320
321 width: _title.width + units.gu(2)
322 height: parent.height
323
324 action: Action {
325 text: model.label.replace("_", "&")
326 }
327
328 Label {
329 id: _title
330 anchors.centerIn: parent
331 text: actionItem.text
332 horizontalAlignment: Text.AlignLeft
333 color: enabled ? theme.palette.normal.backgroundText : theme.palette.disabled.backgroundText
334 }
335 }
336
337 pageDelegate: PanelMenuPage {
338 readonly property bool isCurrent: modelIndex == __applicationMenus.currentMenuIndex
339 onIsCurrentChanged: {
340 if (isCurrent && menuModel) {
341 menuModel.aboutToShow(modelIndex);
342 }
343 }
344
345 menuModel: __applicationMenus.model
346 submenuIndex: modelIndex
347
348 factory: ApplicationMenuItemFactory {
349 rootModel: __applicationMenus.model
350 }
351 }
352
353 enabled: d.enableTouchMenus
354 opacity: d.showTouchMenu ? 1 : 0
355 visible: opacity != 0
356 clip: true
357 Behavior on opacity { LomiriNumberAnimation { duration: LomiriAnimation.SnapDuration } }
358
359 onEnabledChanged: {
360 if (!enabled) hide();
361 }
362 }
363
364 Item {
365 id: panelTitleHolder
366 anchors {
367 left: parent.left
368 leftMargin: units.gu(1)
369 right: __indicators.left
370 rightMargin: units.gu(1)
371 }
372 height: root.minimizedPanelHeight
373
374 Label {
375 id: rowLabel
376 anchors {
377 left: parent.left
378 right: root.partialWidth ? parent.right : parent.left
379 rightMargin: touchMenuIcon.width
380 }
381 objectName: "panelTitle"
382 height: root.minimizedPanelHeight
383 verticalAlignment: Text.AlignVCenter
384 elide: Text.ElideRight
385 maximumLineCount: 1
386 fontSize: "medium"
387 font.weight: Font.Medium
388 color: theme.palette.selected.backgroundText
389 text: (root.partialWidth && !callHint.visible) ? panelState.title : ""
390 opacity: __applicationMenus.visible && !__applicationMenus.expanded
391 Behavior on opacity { NumberAnimation { duration: LomiriAnimation.SnapDuration } }
392 visible: opacity !== 0
393 }
394
395 Icon {
396 id: touchMenuIcon
397 objectName: "touchMenuIcon"
398 anchors {
399 left: parent.left
400 leftMargin: rowLabel.contentWidth + units.dp(2)
401 verticalCenter: parent.verticalCenter
402 }
403 width: units.gu(2)
404 height: units.gu(2)
405 name: "down"
406 color: theme.palette.normal.backgroundText
407 opacity: !__applicationMenus.expanded && d.enableTouchMenus && !callHint.visible
408 Behavior on opacity { NumberAnimation { duration: LomiriAnimation.SnapDuration } }
409 visible: opacity !== 0
410 }
411 }
412
413 PanelMenu {
414 id: __indicators
415 objectName: "indicators"
416
417 anchors {
418 top: parent.top
419 right: parent.right
420 }
421 width: root.menuWidth
422 minimizedPanelHeight: root.minimizedPanelHeight
423 expandedPanelHeight: root.expandedPanelHeight
424 openedHeight: root.height
425 screenIndex: root.screenIndex
426 orientation: root.orientation
427
428 overFlowWidth: width - appMenuClear
429 enableHint: !callHint.active && !fullscreenMode
430 showOnClick: !callHint.visible
431 panelColor: panelAreaBackground.color
432 blurSource: root.blurSource
433 blurRect: Qt.rect(x,
434 0,
435 root.width,
436 root.height)
437
438 // On small screens, the Indicators' handle area is the entire top
439 // bar unless there is an application menu. In that case, our handle
440 // needs to allow for some room to clear the application menu.
441 property var appMenuClear: (d.enableTouchMenus && !partialWidth) ? units.gu(7) : 0
442
443 onShowTapped: {
444 if (callHint.active) {
445 callHint.showLiveCall();
446 }
447 }
448
449 rowItemDelegate: IndicatorItem {
450 id: indicatorItem
451 objectName: identifier+"-panelItem"
452
453 property int ownIndex: index
454 // FIXME: there was logic here to not clash with app menus without clip: true, bring it back with app menus
455 readonly property bool hidden: !expanded && (!indicatorVisible || hideSessionIndicator || hideKeyboardIndicator)
456 // HACK for indicator-session
457 readonly property bool hideSessionIndicator: identifier == "ayatana-indicator-session" && Math.min(Screen.width, Screen.height) <= units.gu(60)
458 // HACK for indicator-keyboard
459 readonly property bool hideKeyboardIndicator: identifier == "ayatana-indicator-keyboard" && !hasKeyboard
460
461 height: parent.height
462 expanded: indicators.expanded
463 finishedExpanding: Math.floor(height) == Math.floor(__indicators.expandedPanelHeight)
464 selected: index == indicators.currentMenuIndex
465
466 identifier: model.identifier
467 busName: indicatorProperties.busName
468 actionsObjectPath: indicatorProperties.actionsObjectPath
469 menuObjectPath: indicatorProperties.menuObjectPath
470
471 opacity: hidden ? 0.0 : 1.0
472 Behavior on opacity { LomiriNumberAnimation { duration: LomiriAnimation.SnapDuration } }
473
474 width: ((expanded || indicatorVisible) && !hideSessionIndicator && !hideKeyboardIndicator) ? implicitWidth : 0
475
476 Behavior on width { LomiriNumberAnimation { duration: LomiriAnimation.SnapDuration } }
477 }
478
479 pageDelegate: PanelMenuPage {
480 objectName: modelData.identifier + "-page"
481 submenuIndex: 0
482
483 menuModel: delegate.menuModel
484
485 factory: IndicatorMenuItemFactory {
486 indicator: {
487 var context = modelData.identifier;
488 if (context && context.indexOf("fake-") === 0) {
489 context = context.substring("fake-".length)
490 }
491 return context;
492 }
493 rootModel: delegate.menuModel
494 }
495
496 IndicatorDelegate {
497 id: delegate
498 busName: modelData.indicatorProperties.busName
499 actionsObjectPath: modelData.indicatorProperties.actionsObjectPath
500 menuObjectPath: modelData.indicatorProperties.menuObjectPath
501 }
502 }
503
504 enabled: !applicationMenus.expanded
505 opacity: !callHint.visible && !applicationMenus.expanded ? 1 : 0
506 clip: true
507 Behavior on opacity { LomiriNumberAnimation { duration: LomiriAnimation.SnapDuration } }
508
509 onEnabledChanged: {
510 if (!enabled) hide();
511 }
512 }
513 }
514
515 IndicatorsLight {
516 id: indicatorLights
517 supportsMultiColorLed: root.supportsMultiColorLed
518 }
519
520 states: [
521 State {
522 name: "onscreen" //fully opaque and visible at top edge of screen
523 when: !fullscreenMode || temporarilyShown
524 PropertyChanges {
525 target: panelArea;
526 anchors.topMargin: 0
527 opacity: 1;
528 }
529 },
530 State {
531 name: "offscreen" //pushed off screen
532 when: fullscreenMode
533 PropertyChanges {
534 target: panelArea;
535 anchors.topMargin: {
536 if (indicators.state !== "initial") return 0;
537 if (applicationMenus.state !== "initial") return 0;
538 return -minimizedPanelHeight;
539 }
540 opacity: indicators.fullyClosed && applicationMenus.fullyClosed ? 0.0 : 1.0
541 }
542 PropertyChanges {
543 target: indicators.showDragHandle;
544 anchors.bottomMargin: -units.gu(1)
545 }
546 PropertyChanges {
547 target: applicationMenus.showDragHandle;
548 anchors.bottomMargin: -units.gu(1)
549 }
550 }
551 ]
552
553 transitions: [
554 Transition {
555 to: "onscreen"
556 LomiriNumberAnimation { target: panelArea; properties: "anchors.topMargin,opacity" }
557 },
558 Transition {
559 to: "offscreen"
560 LomiriNumberAnimation { target: panelArea; properties: "anchors.topMargin,opacity" }
561 }
562 ]
563}