Kirigami2

GlobalDrawer.qml
1/*
2 * SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
3 *
4 * SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6
7pragma ComponentBehavior: Bound
8
9import QtQuick
10import QtQuick.Templates as T
11import QtQuick.Controls as QQC2
12import QtQuick.Layouts
13import org.kde.kirigami as Kirigami
14import "private" as KP
15
16/**
17 * A specialized form of the Drawer intended for showing an application's
18 * always-available global actions. Think of it like a mobile version of
19 * a desktop application's menubar.
20 *
21 * Example usage:
22 * @code
23 * import org.kde.kirigami 2.4 as Kirigami
24 *
25 * Kirigami.ApplicationWindow {
26 * [...]
27 * globalDrawer: Kirigami.GlobalDrawer {
28 * actions: [
29 * Kirigami.Action {
30 * text: "View"
31 * icon.name: "view-list-icons"
32 * Kirigami.Action {
33 * text: "action 1"
34 * }
35 * Kirigami.Action {
36 * text: "action 2"
37 * }
38 * Kirigami.Action {
39 * text: "action 3"
40 * }
41 * },
42 * Kirigami.Action {
43 * text: "Sync"
44 * icon.name: "folder-sync"
45 * }
46 * ]
47 * }
48 * [...]
49 * }
50 * @endcode
51 *
52 */
53Kirigami.OverlayDrawer {
54 id: root
55
56 edge: Qt.application.layoutDirection === Qt.RightToLeft ? Qt.RightEdge : Qt.LeftEdge
57
58 handleClosedIcon.source: null
59 handleOpenIcon.source: null
60
61 handleVisible: {
62 // When drawer is inline with content and opened, there is no point is showing handle.
63 if (!modal && drawerOpen) {
64 return false;
65 }
66
67 // GlobalDrawer can be hidden by controlsVisible...
68 if (typeof applicationWindow === "function") {
69 const w = applicationWindow();
70 if (w && !w.controlsVisible) {
71 return false;
72 }
73 }
74
75 // ...but it still performs additional checks.
76 return !isMenu || Kirigami.Settings.isMobile;
77 }
78
79 enabled: !isMenu || Kirigami.Settings.isMobile
80
81//BEGIN properties
82 /**
83 * @brief This property holds the title displayed at the top of the drawer.
84 * @see org::kde::kirigami::private::BannerImage::title
85 * @property string title
86 */
87 property string title
88
89 /**
90 * @brief This property holds an icon to be displayed alongside the title.
91 * @see org::kde::kirigami::private::BannerImage::titleIcon
92 * @see org::kde::kirigami::Icon::source
93 * @property var titleIcon
94 */
95 property var titleIcon
96
97 /**
98 * @brief This property holds the actions displayed in the drawer.
99 *
100 * The list of actions can be nested having a tree structure.
101 * A tree depth bigger than 2 is discouraged.
102 *
103 * Example usage:
104 *
105 * @code
106 * import org.kde.kirigami as Kirigami
107 *
108 * Kirigami.ApplicationWindow {
109 * globalDrawer: Kirigami.GlobalDrawer {
110 * actions: [
111 * Kirigami.Action {
112 * text: "View"
113 * icon.name: "view-list-icons"
114 * Kirigami.Action {
115 * text: "action 1"
116 * }
117 * Kirigami.Action {
118 * text: "action 2"
119 * }
120 * Kirigami.Action {
121 * text: "action 3"
122 * }
123 * },
124 * Kirigami.Action {
125 * text: "Sync"
126 * icon.name: "folder-sync"
127 * }
128 * ]
129 * }
130 * }
131 * @endcode
132 * @property list<T.Action> actions
133 */
134 property list<T.Action> actions
135
136 /**
137 * @brief This property holds an item that will always be displayed at the top of the drawer.
138 *
139 * If the drawer contents can be scrolled, this item will stay still and won't scroll.
140 *
141 * @note This property is mainly intended for toolbars.
142 * @since 2.12
143 */
144 property alias header: mainLayout.header
145
146 /**
147 * @brief This property holds an item that will always be displayed at the bottom of the drawer.
148 *
149 * If the drawer contents can be scrolled, this item will stay still and won't scroll.
150 *
151 * @note This property is mainly intended for toolbars.
152 * @since 6.0
153 */
154 property alias footer: mainLayout.footer
155
156 /**
157 * @brief This property holds items that are displayed above the actions.
158 *
159 * Example usage:
160 * @code
161 * import org.kde.kirigami 2.4 as Kirigami
162 *
163 * Kirigami.ApplicationWindow {
164 * [...]
165 * globalDrawer: Kirigami.GlobalDrawer {
166 * actions: [...]
167 * topContent: [Button {
168 * text: "Button"
169 * onClicked: //do stuff
170 * }]
171 * }
172 * [...]
173 * }
174 * @endcode
175 * @property list<QtObject> topContent
176 */
177 property alias topContent: topContent.data
178
179 /**
180 * @brief This property holds items that are displayed under the actions.
181 *
182 * Example usage:
183 * @code
184 * import org.kde.kirigami 2.4 as Kirigami
185 *
186 * Kirigami.ApplicationWindow {
187 * [...]
188 * globalDrawer: Kirigami.GlobalDrawer {
189 * actions: [...]
190 * Button {
191 * text: "Button"
192 * onClicked: //do stuff
193 * }
194 * }
195 * [...]
196 * }
197 * @endcode
198 * @note This is a `default` property.
199 * @property list<QtObject> content
200 */
201 default property alias content: mainContent.data
202
203 /**
204 * @brief This property sets whether content items at the top should be shown.
205 * when the drawer is collapsed as a sidebar.
206 *
207 * If you want to keep some items visible and some invisible, set this to
208 * false and control the visibility/opacity of individual items,
209 * binded to the collapsed property
210 *
211 * default: ``false``
212 *
213 * @since 2.5
214 */
215 property bool showTopContentWhenCollapsed: false
216
217 /**
218 * @brief This property sets whether content items at the bottom should be shown.
219 * when the drawer is collapsed as a sidebar.
220 *
221 * If you want to keep some items visible and some invisible, set this to
222 * false and control the visibility/opacity of individual items,
223 * binded to the collapsed property
224 *
225 * default: ``false``
226 *
227 * @see content
228 * @since 2.5
229 */
230 property bool showContentWhenCollapsed: false
231
232 // TODO
233 property bool showHeaderWhenCollapsed: false
234
235 /**
236 * @brief This property sets whether activating a leaf action resets the
237 * menu to show leaf's parent actions.
238 *
239 * A leaf action is an action without any child actions.
240 *
241 * default: ``true``
242 */
243 property bool resetMenuOnTriggered: true
244
245 /**
246 * @brief This property points to the action acting as a submenu
247 */
248 readonly property T.Action currentSubMenu: stackView.currentItem?.current ?? null
249
250 /**
251 * @brief This property sets whether the drawer becomes a menu on the desktop.
252 *
253 * default: ``false``
254 *
255 * @since 2.11
256 */
257 property bool isMenu: false
258
259 /**
260 * @brief This property sets the visibility of the collapse button
261 * when the drawer collapsible.
262 *
263 * default: ``true``
264 *
265 * @since 2.12
266 */
267 property bool collapseButtonVisible: true
268//END properties
269
270 /**
271 * @brief This function reverts the menu back to its initial state
272 */
273 function resetMenu() {
274 stackView.pop(stackView.get(0, T.StackView.DontLoad));
275 if (root.modal) {
276 root.drawerOpen = false;
277 }
278 }
279
280 // rightPadding: !Kirigami.Settings.isMobile && mainFlickable.contentHeight > mainFlickable.height ? Kirigami.Units.gridUnit : Kirigami.Units.smallSpacing
281
282 Kirigami.Theme.colorSet: modal ? Kirigami.Theme.Window : Kirigami.Theme.View
283
284 onIsMenuChanged: drawerOpen = false
285
286 Component {
287 id: menuComponent
288
289 Column {
290 property alias model: actionsRepeater.model
291 property T.Action current
292 property int level: 0
293
294 spacing: 0
295 Layout.maximumHeight: Layout.minimumHeight
296
297 QQC2.ItemDelegate {
298 id: backItem
299
300 visible: level > 0
301 width: parent.width
302 icon.name: mirrored ? "go-previous-symbolic-rtl" : "go-previous-symbolic"
303
304 text: Kirigami.MnemonicData.richTextLabel
305 activeFocusOnTab: true
306
307 Kirigami.MnemonicData.enabled: enabled && visible
308 Kirigami.MnemonicData.controlType: Kirigami.MnemonicData.MenuItem
309 Kirigami.MnemonicData.label: qsTr("Back")
310
311 onClicked: stackView.pop()
312
313 Keys.onEnterPressed: stackView.pop()
314 Keys.onReturnPressed: stackView.pop()
315
316 Keys.onDownPressed: nextItemInFocusChain().focus = true
317 Keys.onUpPressed: nextItemInFocusChain(false).focus = true
318 }
319
320 Shortcut {
321 sequence: backItem.Kirigami.MnemonicData.sequence
322 onActivated: backItem.clicked()
323 }
324
325 Repeater {
326 id: actionsRepeater
327
328 readonly property bool withSections: {
329 for (const action of root.actions) {
330 if (action.hasOwnProperty("expandible") && action.expandible) {
331 return true;
332 }
333 }
334 return false;
335 }
336
337 model: root.actions
338
339 delegate: ActionDelegate {
340 required property T.Action modelData
341
342 tAction: modelData
343 withSections: actionsRepeater.withSections
344 }
345 }
346 }
347 }
348
349 component ActionDelegate : Column {
350 id: delegate
351
352 required property int index
353 required property T.Action tAction
354 required property bool withSections
355
356 // `as` case operator is still buggy
357 readonly property Kirigami.Action kAction: tAction instanceof Kirigami.Action ? tAction : null
358
359 readonly property bool isExpanded: {
360 return !root.collapsed
361 && kAction
362 && kAction.expandible
363 && kAction.children.length > 0;
364 }
365
366 visible: kAction?.visible ?? true
367
368 width: parent.width
369
370 KP.GlobalDrawerActionItem {
371 Kirigami.Theme.colorSet: !root.modal && !root.collapsed && delegate.withSections
372 ? Kirigami.Theme.Window : parent.Kirigami.Theme.colorSet
373
374 visible: !delegate.isExpanded
375 width: parent.width
376
377 tAction: delegate.tAction
378
379 onCheckedChanged: {
380 // move every checked item into view
381 if (checked && topContent.height + backItem.height + (delegate.index + 1) * height - mainFlickable.contentY > mainFlickable.height) {
382 mainFlickable.contentY += height
383 }
384 }
385 }
386
387 Item {
388 id: headerItem
389
390 visible: delegate.isExpanded
391 height: sectionHeader.implicitHeight
392 width: parent.width
393
394 Kirigami.ListSectionHeader {
395 id: sectionHeader
396
397 anchors.fill: parent
398 Kirigami.Theme.colorSet: root.modal ? Kirigami.Theme.View : Kirigami.Theme.Window
399
400 contentItem: RowLayout {
401 spacing: sectionHeader.spacing
402
403 Kirigami.Icon {
404 property int size: Kirigami.Units.iconSizes.smallMedium
405 Layout.minimumHeight: size
406 Layout.maximumHeight: size
407 Layout.minimumWidth: size
408 Layout.maximumWidth: size
409 source: delegate.tAction.icon.name || delegate.tAction.icon.source
410 }
411
412 Kirigami.Heading {
413 level: 4
414 text: delegate.tAction.text
415 elide: Text.ElideRight
416 Layout.fillWidth: true
417 }
418 }
419 }
420 }
421
422 Repeater {
423 model: delegate.isExpanded ? (delegate.kAction?.children ?? null) : null
424
425 NestedActionDelegate {
426 required property T.Action modelData
427
428 tAction: modelData
429 withSections: delegate.withSections
430 }
431 }
432 }
433
434 component NestedActionDelegate : KP.GlobalDrawerActionItem {
435 required property bool withSections
436
437 width: parent.width
438 opacity: !root.collapsed
439 leftPadding: withSections && !root.collapsed && !root.modal ? padding * 2 : padding * 4
440 }
441
442 contentItem: Kirigami.HeaderFooterLayout {
443 id: mainLayout
444
445 anchors {
446 fill: parent
447 topMargin: root.collapsed && !showHeaderWhenCollapsed ? -contentItem.y : 0
448 }
449
450 Behavior on anchors.topMargin {
451 NumberAnimation {
452 duration: Kirigami.Units.longDuration
453 easing.type: Easing.InOutQuad
454 }
455 }
456
457 header: RowLayout {
458 visible: root.title.length > 0 || Boolean(root.titleIcon)
459 spacing: Kirigami.Units.largeSpacing
460
461 Kirigami.Icon {
462 source: root.titleIcon
463 }
464
465 Kirigami.Heading {
466 text: root.title
467 elide: Text.ElideRight
468 visible: !root.collapsed
469 Layout.fillWidth: true
470 }
471 }
472
473 contentItem: QQC2.ScrollView {
474 id: scrollView
475
476 //ensure the attached property exists
477 Kirigami.Theme.inherit: true
478
479 // HACK: workaround for https://bugreports.qt.io/browse/QTBUG-83890
480 QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
481
482 implicitWidth: Math.min(Kirigami.Units.gridUnit * 20, root.parent.width * 0.8)
483
484 Flickable {
485 id: mainFlickable
486
487 contentWidth: width
488 contentHeight: mainColumn.Layout.minimumHeight
489
490 clip: root.footer !== null
491
492 ColumnLayout {
493 id: mainColumn
494 width: mainFlickable.width
495 spacing: 0
496 height: Math.max(scrollView.height, Layout.minimumHeight)
497
498 ColumnLayout {
499 id: topContent
500
501 spacing: 0
502
503 Layout.alignment: Qt.AlignHCenter
504 Layout.leftMargin: root.leftPadding
505 Layout.rightMargin: root.rightPadding
506 Layout.bottomMargin: Kirigami.Units.smallSpacing
507 Layout.topMargin: root.topPadding
508 Layout.fillWidth: true
509 Layout.fillHeight: true
510 Layout.preferredHeight: implicitHeight * opacity
511 // NOTE: why this? just Layout.fillWidth: true doesn't seem sufficient
512 // as items are added only after this column creation
513 Layout.minimumWidth: parent.width - root.leftPadding - root.rightPadding
514
515 visible: children.length > 0 && childrenRect.height > 0 && opacity > 0
516 opacity: !root.collapsed || showTopContentWhenCollapsed
517
518 Behavior on opacity {
519 // not an animator as is binded
520 NumberAnimation {
521 duration: Kirigami.Units.longDuration
522 easing.type: Easing.InOutQuad
523 }
524 }
525 }
526
527 T.StackView {
528 id: stackView
529
530 property KP.ActionsMenu openSubMenu
531
532 clip: true
533 Layout.fillWidth: true
534 Layout.minimumHeight: currentItem ? currentItem.implicitHeight : 0
535 Layout.maximumHeight: Layout.minimumHeight
536
537 initialItem: menuComponent
538
539 // NOTE: it's important those are NumberAnimation and not XAnimators
540 // as while the animation is running the drawer may close, and
541 // the animator would stop when not drawing see BUG 381576
542 popEnter: Transition {
543 NumberAnimation { property: "x"; from: (stackView.mirrored ? -1 : 1) * -stackView.width; to: 0; duration: Kirigami.Units.veryLongDuration; easing.type: Easing.OutCubic }
544 }
545
546 popExit: Transition {
547 NumberAnimation { property: "x"; from: 0; to: (stackView.mirrored ? -1 : 1) * stackView.width; duration: Kirigami.Units.veryLongDuration; easing.type: Easing.OutCubic }
548 }
549
550 pushEnter: Transition {
551 NumberAnimation { property: "x"; from: (stackView.mirrored ? -1 : 1) * stackView.width; to: 0; duration: Kirigami.Units.veryLongDuration; easing.type: Easing.OutCubic }
552 }
553
554 pushExit: Transition {
555 NumberAnimation { property: "x"; from: 0; to: (stackView.mirrored ? -1 : 1) * -stackView.width; duration: Kirigami.Units.veryLongDuration; easing.type: Easing.OutCubic }
556 }
557
558 replaceEnter: Transition {
559 NumberAnimation { property: "x"; from: (stackView.mirrored ? -1 : 1) * stackView.width; to: 0; duration: Kirigami.Units.veryLongDuration; easing.type: Easing.OutCubic }
560 }
561
562 replaceExit: Transition {
563 NumberAnimation { property: "x"; from: 0; to: (stackView.mirrored ? -1 : 1) * -stackView.width; duration: Kirigami.Units.veryLongDuration; easing.type: Easing.OutCubic }
564 }
565 }
566
567 Item {
568 Layout.fillWidth: true
569 Layout.fillHeight: root.actions.length > 0
570 Layout.minimumHeight: Kirigami.Units.smallSpacing
571 }
572
573 ColumnLayout {
574 id: mainContent
575 Layout.alignment: Qt.AlignHCenter
576 Layout.leftMargin: root.leftPadding
577 Layout.rightMargin: root.rightPadding
578 Layout.fillWidth: true
579 Layout.fillHeight: true
580 // NOTE: why this? just Layout.fillWidth: true doesn't seem sufficient
581 // as items are added only after this column creation
582 Layout.minimumWidth: parent.width - root.leftPadding - root.rightPadding
583 visible: children.length > 0 && (opacity > 0 || mainContentAnimator.running)
584 opacity: !root.collapsed || showContentWhenCollapsed
585 Behavior on opacity {
586 OpacityAnimator {
587 id: mainContentAnimator
588 duration: Kirigami.Units.longDuration
589 easing.type: Easing.InOutQuad
590 }
591 }
592 }
593
594 Item {
595 Layout.minimumWidth: Kirigami.Units.smallSpacing
596 Layout.minimumHeight: root.bottomPadding
597 }
598
599 QQC2.ToolButton {
600 Layout.fillWidth: true
601
602 icon.name: {
603 if (root.collapsible && root.collapseButtonVisible) {
604 // Check for edge regardless of RTL/locale/mirrored status,
605 // because edge can be set externally.
606 const mirrored = root.edge === Qt.RightEdge;
607
608 if (root.collapsed) {
609 return mirrored ? "sidebar-expand-right" : "sidebar-expand-left";
610 } else {
611 return mirrored ? "sidebar-collapse-right" : "sidebar-collapse-left";
612 }
613 }
614 return "";
615 }
616
617 visible: root.collapsible && root.collapseButtonVisible
618 text: root.collapsed ? "" : qsTr("Close Sidebar")
619
620 onClicked: root.collapsed = !root.collapsed
621
622 QQC2.ToolTip.visible: root.collapsed && (Kirigami.Settings.tabletMode ? pressed : hovered)
623 QQC2.ToolTip.text: qsTr("Open Sidebar")
624 QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
625 }
626 }
627 }
628 }
629 }
630}
An item that represents an abstract Action.
Definition Action.qml:15
replicates a little part of what Page does, It's a container with 3 properties, header,...
Type type(const QSqlDatabase &db)
QStringView level(QStringView ifopt)
QString name(StandardShortcut id)
pointer data()
qsizetype length() const const
AlignHCenter
ElideRight
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri May 3 2024 11:49:34 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.