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"
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
46 property alias applicationMenus: __applicationMenus
47 property alias indicators: __indicators
48 property bool fullscreenMode: false
49 property real panelAreaShowProgress: 1.0
50 property bool greeterShown: false
51 property bool hasKeyboard: false
52 property bool supportsMultiColorLed: true
53 property var blurSource : null
54 property bool lightMode : false
55
56 // Whether our expanded menus should take up the full width of the panel
57 property bool partialWidth: width >= units.gu(60)
58
59 property string mode: "staged"
60 property PanelState panelState
61
62 property bool temporarilyShown: false
63
64 function temporarilyShow() {
65 temporarilyShown = true
66 temporaryShowTimeout.restart()
67 }
68
69 Timer {
70 id: temporaryShowTimeout
71 running: false
72 interval: 2000
73 onTriggered: {
74 temporarilyShown = false
75 }
76 }
77
78 MouseArea {
79 id: backMouseEater
80 anchors.fill: parent
81 anchors.topMargin: panelHeight
82 visible: !indicators.fullyClosed || !applicationMenus.fullyClosed
83 enabled: visible
84 hoverEnabled: true // should also eat hover events, otherwise they will pass through
85
86 onClicked: {
87 __applicationMenus.hide();
88 __indicators.hide();
89 }
90 }
91
92 Binding {
93 target: panelState
94 restoreMode: Binding.RestoreBinding
95 property: "panelHeight"
96 value: minimizedPanelHeight
97 }
98
99 RegisteredApplicationMenuModel {
100 id: registeredMenuModel
101 persistentSurfaceId: panelState.focusedPersistentSurfaceId
102 }
103
104 QtObject {
105 id: d
106
107 property bool revealControls: !greeterShown &&
108 !applicationMenus.shown &&
109 !indicators.shown &&
110 (decorationMouseArea.containsMouse || menuBarLoader.menusRequested)
111
112 property bool showWindowDecorationControls: (revealControls && panelState.decorationsVisible) ||
113 panelState.decorationsAlwaysVisible
114
115 property bool showPointerMenu: revealControls &&
116 (panelState.decorationsVisible || mode == "windowed")
117
118 property bool enablePointerMenu: applicationMenus.available &&
119 applicationMenus.model
120
121 property bool showTouchMenu: !greeterShown &&
122 !showPointerMenu &&
123 !showWindowDecorationControls
124
125 property bool enableTouchMenus: showTouchMenu &&
126 applicationMenus.available &&
127 applicationMenus.model
128 }
129
130 Item {
131 id: panelArea
132 objectName: "panelArea"
133
134 anchors.fill: parent
135
136 transform: Translate {
137 y: indicators.state === "initial"
138 ? (1.0 - panelAreaShowProgress) * - minimizedPanelHeight
139 : 0
140 }
141
142 BorderImage {
143 id: indicatorsDropShadow
144 anchors {
145 fill: __indicators
146 margins: -units.gu(1)
147 }
148 visible: !__indicators.fullyClosed
149 source: "graphics/rectangular_dropshadow.sci"
150 }
151
152 BorderImage {
153 id: appmenuDropShadow
154 anchors {
155 fill: __applicationMenus
156 margins: -units.gu(1)
157 }
158 visible: !__applicationMenus.fullyClosed
159 source: "graphics/rectangular_dropshadow.sci"
160 }
161
162 BorderImage {
163 id: panelDropShadow
164 anchors {
165 fill: panelAreaBackground
166 bottomMargin: -units.gu(1)
167 }
168 visible: panelState.dropShadow
169 source: "graphics/rectangular_dropshadow.sci"
170 }
171
172 Rectangle {
173 id: panelAreaBackground
174 color: callHint.visible ? theme.palette.normal.activity :
175 (root.lightMode ? "#FFFFFF" : "#000000")
176 anchors {
177 top: parent.top
178 left: parent.left
179 right: parent.right
180 }
181 height: minimizedPanelHeight
182
183 Behavior on color { ColorAnimation { duration: LomiriAnimation.FastDuration } }
184 }
185
186 MouseArea {
187 id: decorationMouseArea
188 objectName: "windowControlArea"
189 anchors {
190 left: parent.left
191 right: parent.right
192 }
193 height: minimizedPanelHeight
194 hoverEnabled: !__indicators.shown
195 onClicked: {
196 if (callHint.visible) {
197 callHint.showLiveCall();
198 }
199 }
200
201 onPressed: {
202 if (!callHint.visible) {
203 // let it fall through to the window decoration of the maximized window behind, if any
204 mouse.accepted = false;
205 }
206 var menubar = menuBarLoader.item;
207 if (menubar) {
208 menubar.invokeMenu(mouse);
209 }
210 }
211
212 Row {
213 anchors.fill: parent
214 spacing: units.gu(2)
215
216 // WindowControlButtons inside the mouse area, otherwise QML doesn't grok nested hover events :/
217 // cf. https://bugreports.qt.io/browse/QTBUG-32909
218 WindowControlButtons {
219 id: windowControlButtons
220 objectName: "panelWindowControlButtons"
221 height: indicators.minimizedPanelHeight
222 opacity: d.showWindowDecorationControls ? 1 : 0
223 visible: opacity != 0
224 Behavior on opacity { LomiriNumberAnimation { duration: LomiriAnimation.SnapDuration } }
225
226 active: panelState.decorationsVisible || panelState.decorationsAlwaysVisible
227 windowIsMaximized: true
228 onCloseClicked: panelState.closeClicked()
229 onMinimizeClicked: panelState.minimizeClicked()
230 onMaximizeClicked: panelState.restoreClicked()
231 closeButtonShown: panelState.closeButtonShown
232 }
233
234 Loader {
235 id: menuBarLoader
236 objectName: "menuBarLoader"
237 height: parent.height
238 enabled: d.enablePointerMenu
239 opacity: d.showPointerMenu ? 1 : 0
240 visible: opacity != 0
241 Behavior on opacity { LomiriNumberAnimation { duration: LomiriAnimation.SnapDuration } }
242 active: d.showPointerMenu && !callHint.visible
243
244 width: parent.width - windowControlButtons.width - units.gu(2) - __indicators.barWidth
245
246 readonly property bool menusRequested: menuBarLoader.item ? menuBarLoader.item.showRequested : false
247
248 sourceComponent: MenuBar {
249 id: bar
250 objectName: "menuBar"
251 anchors.left: menuBarLoader ? menuBarLoader.left : undefined
252 anchors.margins: units.gu(1)
253 height: menuBarLoader.height
254 enableKeyFilter: valid && panelState.decorationsVisible
255 lomiriMenuModel: __applicationMenus.model
256 panelState: root.panelState
257
258 Connections {
259 target: __applicationMenus
260 function onShownChanged() { bar.dismiss(); }
261 }
262
263 Connections {
264 target: __indicators
265 function onShownChanged() { bar.dismiss(); }
266 }
267
268 onDoubleClicked: panelState.restoreClicked()
269 onPressed: mouse.accepted = false // let the parent mouse area handle this, so it can both unsnap window and show menu
270 }
271 }
272 }
273
274 ActiveCallHint {
275 id: callHint
276 objectName: "callHint"
277
278 anchors.centerIn: parent
279 height: minimizedPanelHeight
280
281 visible: active && indicators.state == "initial" && __applicationMenus.state == "initial"
282 greeterShown: root.greeterShown
283 }
284 }
285
286 PanelMenu {
287 id: __applicationMenus
288
289 x: menuContentX
290 model: registeredMenuModel.model
291 width: root.menuWidth
292 overFlowWidth: width
293 minimizedPanelHeight: root.minimizedPanelHeight
294 expandedPanelHeight: root.expandedPanelHeight
295 openedHeight: root.height
296 alignment: Qt.AlignLeft
297 enableHint: !callHint.active && !fullscreenMode
298 showOnClick: false
299 panelColor: panelAreaBackground.color
300 blurSource: root.blurSource
301 blurRect: Qt.rect(x,
302 0,
303 root.width,
304 root.height)
305 lightMode: root.lightMode
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}