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