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

KDE's Doxygen guidelines are available online.