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 property var blurSource : null
53 property bool lightMode : false
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 :
157 (root.lightMode ? "#FFFFFF" : "#000000")
158 anchors {
159 top: parent.top
160 left: parent.left
161 right: parent.right
162 }
163 height: minimizedPanelHeight
164
165 Behavior on color { ColorAnimation { duration: LomiriAnimation.FastDuration } }
166 }
167
168 MouseArea {
169 id: decorationMouseArea
170 objectName: "windowControlArea"
171 anchors {
172 left: parent.left
173 right: parent.right
174 }
175 height: minimizedPanelHeight
176 hoverEnabled: !__indicators.shown
177 onClicked: {
178 if (callHint.visible) {
179 callHint.showLiveCall();
180 }
181 }
182
183 onPressed: {
184 if (!callHint.visible) {
185 // let it fall through to the window decoration of the maximized window behind, if any
186 mouse.accepted = false;
187 }
188 var menubar = menuBarLoader.item;
189 if (menubar) {
190 menubar.invokeMenu(mouse);
191 }
192 }
193
194 Row {
195 anchors.fill: parent
196 spacing: units.gu(2)
197
198 // WindowControlButtons inside the mouse area, otherwise QML doesn't grok nested hover events :/
199 // cf. https://bugreports.qt.io/browse/QTBUG-32909
200 WindowControlButtons {
201 id: windowControlButtons
202 objectName: "panelWindowControlButtons"
203 height: indicators.minimizedPanelHeight
204 opacity: d.showWindowDecorationControls ? 1 : 0
205 visible: opacity != 0
206 Behavior on opacity { LomiriNumberAnimation { duration: LomiriAnimation.SnapDuration } }
207
208 active: panelState.decorationsVisible || panelState.decorationsAlwaysVisible
209 windowIsMaximized: true
210 onCloseClicked: panelState.closeClicked()
211 onMinimizeClicked: panelState.minimizeClicked()
212 onMaximizeClicked: panelState.restoreClicked()
213 closeButtonShown: panelState.closeButtonShown
214 }
215
216 Loader {
217 id: menuBarLoader
218 objectName: "menuBarLoader"
219 height: parent.height
220 enabled: d.enablePointerMenu
221 opacity: d.showPointerMenu ? 1 : 0
222 visible: opacity != 0
223 Behavior on opacity { LomiriNumberAnimation { duration: LomiriAnimation.SnapDuration } }
224 active: d.showPointerMenu && !callHint.visible
225
226 width: parent.width - windowControlButtons.width - units.gu(2) - __indicators.barWidth
227
228 readonly property bool menusRequested: menuBarLoader.item ? menuBarLoader.item.showRequested : false
229
230 sourceComponent: MenuBar {
231 id: bar
232 objectName: "menuBar"
233 anchors.left: menuBarLoader ? menuBarLoader.left : undefined
234 anchors.margins: units.gu(1)
235 height: menuBarLoader.height
236 enableKeyFilter: valid && panelState.decorationsVisible
237 lomiriMenuModel: __applicationMenus.model
238 panelState: root.panelState
239
240 Connections {
241 target: __applicationMenus
242 onShownChanged: bar.dismiss();
243 }
244
245 Connections {
246 target: __indicators
247 onShownChanged: bar.dismiss();
248 }
249
250 onDoubleClicked: panelState.restoreClicked()
251 onPressed: mouse.accepted = false // let the parent mouse area handle this, so it can both unsnap window and show menu
252 }
253 }
254 }
255
256 ActiveCallHint {
257 id: callHint
258 objectName: "callHint"
259
260 anchors.centerIn: parent
261 height: minimizedPanelHeight
262
263 visible: active && indicators.state == "initial" && __applicationMenus.state == "initial"
264 greeterShown: root.greeterShown
265 }
266 }
267
268 PanelMenu {
269 id: __applicationMenus
270
271 x: menuContentX
272 model: registeredMenuModel.model
273 width: root.menuWidth
274 overFlowWidth: width
275 minimizedPanelHeight: root.minimizedPanelHeight
276 expandedPanelHeight: root.expandedPanelHeight
277 openedHeight: root.height
278 alignment: Qt.AlignLeft
279 enableHint: !callHint.active && !fullscreenMode
280 showOnClick: false
281 panelColor: panelAreaBackground.color
282 blurSource: root.blurSource
283 blurRect: Qt.rect(x,
284 0,
285 root.width,
286 root.height)
287 lightMode: root.lightMode
288
289 onShowTapped: {
290 if (callHint.active) {
291 callHint.showLiveCall();
292 }
293 }
294
295 hideRow: !expanded
296 rowItemDelegate: ActionItem {
297 id: actionItem
298 property int ownIndex: index
299 objectName: "appMenuItem"+index
300 enabled: model.sensitive
301
302 width: _title.width + units.gu(2)
303 height: parent.height
304
305 action: Action {
306 text: model.label.replace("_", "&")
307 }
308
309 Label {
310 id: _title
311 anchors.centerIn: parent
312 text: actionItem.text
313 horizontalAlignment: Text.AlignLeft
314 color: enabled ? theme.palette.normal.backgroundText : theme.palette.disabled.backgroundText
315 }
316 }
317
318 pageDelegate: PanelMenuPage {
319 readonly property bool isCurrent: modelIndex == __applicationMenus.currentMenuIndex
320 onIsCurrentChanged: {
321 if (isCurrent && menuModel) {
322 menuModel.aboutToShow(modelIndex);
323 }
324 }
325
326 menuModel: __applicationMenus.model
327 submenuIndex: modelIndex
328
329 factory: ApplicationMenuItemFactory {
330 rootModel: __applicationMenus.model
331 }
332 }
333
334 enabled: d.enableTouchMenus
335 opacity: d.showTouchMenu ? 1 : 0
336 visible: opacity != 0
337 clip: true
338 Behavior on opacity { LomiriNumberAnimation { duration: LomiriAnimation.SnapDuration } }
339
340 onEnabledChanged: {
341 if (!enabled) hide();
342 }
343 }
344
345 Item {
346 id: panelTitleHolder
347 anchors {
348 left: parent.left
349 leftMargin: units.gu(1)
350 right: __indicators.left
351 rightMargin: units.gu(1)
352 }
353 height: root.minimizedPanelHeight
354
355 Label {
356 id: rowLabel
357 anchors {
358 left: parent.left
359 right: root.partialWidth ? parent.right : parent.left
360 rightMargin: touchMenuIcon.width
361 }
362 objectName: "panelTitle"
363 height: root.minimizedPanelHeight
364 verticalAlignment: Text.AlignVCenter
365 elide: Text.ElideRight
366 maximumLineCount: 1
367 fontSize: "medium"
368 font.weight: Font.Medium
369 color: theme.palette.selected.backgroundText
370 text: (root.partialWidth && !callHint.visible) ? panelState.title : ""
371 opacity: __applicationMenus.visible && !__applicationMenus.expanded
372 Behavior on opacity { NumberAnimation { duration: LomiriAnimation.SnapDuration } }
373 visible: opacity !== 0
374 }
375
376 Icon {
377 id: touchMenuIcon
378 objectName: "touchMenuIcon"
379 anchors {
380 left: parent.left
381 leftMargin: rowLabel.contentWidth + units.dp(2)
382 verticalCenter: parent.verticalCenter
383 }
384 width: units.gu(2)
385 height: units.gu(2)
386 name: "down"
387 color: theme.palette.normal.backgroundText
388 opacity: !__applicationMenus.expanded && d.enableTouchMenus && !callHint.visible
389 Behavior on opacity { NumberAnimation { duration: LomiriAnimation.SnapDuration } }
390 visible: opacity !== 0
391 }
392 }
393
394 PanelMenu {
395 id: __indicators
396 objectName: "indicators"
397
398 anchors {
399 top: parent.top
400 right: parent.right
401 }
402 width: root.menuWidth
403 minimizedPanelHeight: root.minimizedPanelHeight
404 expandedPanelHeight: root.expandedPanelHeight
405 openedHeight: root.height
406
407 overFlowWidth: width - appMenuClear
408 enableHint: !callHint.active && !fullscreenMode
409 showOnClick: !callHint.visible
410 panelColor: panelAreaBackground.color
411 blurSource: root.blurSource
412 blurRect: Qt.rect(x,
413 0,
414 root.width,
415 root.height)
416
417 // On small screens, the Indicators' handle area is the entire top
418 // bar unless there is an application menu. In that case, our handle
419 // needs to allow for some room to clear the application menu.
420 property var appMenuClear: (d.enableTouchMenus && !partialWidth) ? units.gu(7) : 0
421
422 onShowTapped: {
423 if (callHint.active) {
424 callHint.showLiveCall();
425 }
426 }
427
428 rowItemDelegate: IndicatorItem {
429 id: indicatorItem
430 objectName: identifier+"-panelItem"
431
432 property int ownIndex: index
433 readonly property bool overflow: parent.width - (x - __indicators.rowContentX) > __indicators.overFlowWidth
434 readonly property bool hidden: !expanded && (overflow || !indicatorVisible || hideSessionIndicator || hideKeyboardIndicator)
435 // HACK for indicator-session
436 readonly property bool hideSessionIndicator: identifier == "ayatana-indicator-session" && Math.min(Screen.width, Screen.height) <= units.gu(60)
437 // HACK for indicator-keyboard
438 readonly property bool hideKeyboardIndicator: identifier == "ayatana-indicator-keyboard" && !hasKeyboard
439
440 height: parent.height
441 expanded: indicators.expanded
442 selected: ListView.isCurrentItem
443
444 identifier: model.identifier
445 busName: indicatorProperties.busName
446 actionsObjectPath: indicatorProperties.actionsObjectPath
447 menuObjectPath: indicatorProperties.menuObjectPath
448
449 opacity: hidden ? 0.0 : 1.0
450 Behavior on opacity { LomiriNumberAnimation { duration: LomiriAnimation.SnapDuration } }
451
452 width: ((expanded || indicatorVisible) && !hideSessionIndicator && !hideKeyboardIndicator) ? implicitWidth : 0
453
454 Behavior on width { LomiriNumberAnimation { duration: LomiriAnimation.SnapDuration } }
455 }
456
457 pageDelegate: PanelMenuPage {
458 objectName: modelData.identifier + "-page"
459 submenuIndex: 0
460
461 menuModel: delegate.menuModel
462
463 factory: IndicatorMenuItemFactory {
464 indicator: {
465 var context = modelData.identifier;
466 if (context && context.indexOf("fake-") === 0) {
467 context = context.substring("fake-".length)
468 }
469 return context;
470 }
471 rootModel: delegate.menuModel
472 }
473
474 IndicatorDelegate {
475 id: delegate
476 busName: modelData.indicatorProperties.busName
477 actionsObjectPath: modelData.indicatorProperties.actionsObjectPath
478 menuObjectPath: modelData.indicatorProperties.menuObjectPath
479 }
480 }
481
482 enabled: !applicationMenus.expanded
483 opacity: !callHint.visible && !applicationMenus.expanded ? 1 : 0
484 clip: true
485 Behavior on opacity { LomiriNumberAnimation { duration: LomiriAnimation.SnapDuration } }
486
487 onEnabledChanged: {
488 if (!enabled) hide();
489 }
490 }
491 }
492
493 IndicatorsLight {
494 id: indicatorLights
495 supportsMultiColorLed: root.supportsMultiColorLed
496 }
497
498 states: [
499 State {
500 name: "onscreen" //fully opaque and visible at top edge of screen
501 when: !fullscreenMode
502 PropertyChanges {
503 target: panelArea;
504 anchors.topMargin: 0
505 opacity: 1;
506 }
507 },
508 State {
509 name: "offscreen" //pushed off screen
510 when: fullscreenMode
511 PropertyChanges {
512 target: panelArea;
513 anchors.topMargin: {
514 if (indicators.state !== "initial") return 0;
515 if (applicationMenus.state !== "initial") return 0;
516 return -minimizedPanelHeight;
517 }
518 opacity: indicators.fullyClosed && applicationMenus.fullyClosed ? 0.0 : 1.0
519 }
520 PropertyChanges {
521 target: indicators.showDragHandle;
522 anchors.bottomMargin: -units.gu(1)
523 }
524 PropertyChanges {
525 target: applicationMenus.showDragHandle;
526 anchors.bottomMargin: -units.gu(1)
527 }
528 }
529 ]
530
531 transitions: [
532 Transition {
533 to: "onscreen"
534 LomiriNumberAnimation { target: panelArea; properties: "anchors.topMargin,opacity" }
535 },
536 Transition {
537 to: "offscreen"
538 LomiriNumberAnimation { target: panelArea; properties: "anchors.topMargin,opacity" }
539 }
540 ]
541}