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