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