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 activeFocusOnTab: true
342
343 Kirigami.MnemonicData.enabled: enabled && visible
344 Kirigami.MnemonicData.controlType: Kirigami.MnemonicData.MenuItem
345 Kirigami.MnemonicData.label: qsTr("Back")
346
347 onClicked: stackView.pop()
348
349 Keys.onEnterPressed: stackView.pop()
350 Keys.onReturnPressed: stackView.pop()
351
352 Keys.onDownPressed: nextItemInFocusChain().focus = true
353 Keys.onUpPressed: nextItemInFocusChain(false).focus = true
354 }
355
356 Shortcut {
357 sequence: backItem.Kirigami.MnemonicData.sequence
358 onActivated: backItem.clicked()
359 }
360
361 Repeater {
362 id: actionsRepeater
363
364 readonly property bool withSections: {
365 for (const action of root.actions) {
366 if (action.hasOwnProperty("expandible") && action.expandible) {
367 return true;
368 }
369 }
370 return false;
371 }
372
373 model: root.actions
374
375 delegate: ActionDelegate {
376 required property T.Action modelData
377
378 tAction: modelData
379 withSections: actionsRepeater.withSections
380 }
381 }
382 }
383 }
384
385 component ActionDelegate : Column {
386 id: delegate
387
388 required property int index
389 required property T.Action tAction
390 required property bool withSections
391
392 // `as` case operator is still buggy
393 readonly property Kirigami.Action kAction: tAction instanceof Kirigami.Action ? tAction : null
394
395 readonly property bool isExpanded: {
396 return !root.collapsed
397 && kAction
398 && kAction.expandible
399 && kAction.children.length > 0;
400 }
401
402 visible: kAction?.visible ?? true
403
404 width: parent.width
405
406 KP.GlobalDrawerActionItem {
407 Kirigami.Theme.colorSet: !root.modal && !root.collapsed && delegate.withSections
408 ? Kirigami.Theme.Window : parent.Kirigami.Theme.colorSet
409
410 visible: !delegate.isExpanded
411 width: parent.width
412
413 tAction: delegate.tAction
414
415 onCheckedChanged: {
416 // move every checked item into view
417 if (checked && topContent.height + backItem.height + (delegate.index + 1) * height - mainFlickable.contentY > mainFlickable.height) {
418 mainFlickable.contentY += height
419 }
420 }
421
422 onFocusChanged: {
423 if (focus) {
424 root.ensureVisible (delegate, topContent.height + (backItem.visible ? backItem.height : 0))
425 }
426 }
427 }
428
429 Item {
430 id: headerItem
431
432 visible: delegate.isExpanded
433 height: sectionHeader.implicitHeight
434 width: parent.width
435
436 Kirigami.ListSectionHeader {
437 id: sectionHeader
438
439 anchors.fill: parent
440 Kirigami.Theme.colorSet: root.modal ? Kirigami.Theme.View : Kirigami.Theme.Window
441
442 contentItem: RowLayout {
443 spacing: sectionHeader.spacing
444
445 Kirigami.Icon {
446 property int size: Kirigami.Units.iconSizes.smallMedium
447 Layout.minimumHeight: size
448 Layout.maximumHeight: size
449 Layout.minimumWidth: size
450 Layout.maximumWidth: size
451 source: delegate.tAction.icon.name || delegate.tAction.icon.source
452 }
453
454 Kirigami.Heading {
455 level: 4
456 text: delegate.tAction.text
457 elide: Text.ElideRight
458 Layout.fillWidth: true
459 }
460 }
461 }
462 }
463
464 Repeater {
465 model: delegate.isExpanded ? (delegate.kAction?.children ?? null) : null
466
467 NestedActionDelegate {
468 required property T.Action modelData
469
470 tAction: modelData
471 withSections: delegate.withSections
472 }
473 }
474 }
475
476 component NestedActionDelegate : KP.GlobalDrawerActionItem {
477 required property bool withSections
478
479 width: parent.width
480 opacity: !root.collapsed
481 leftPadding: withSections && !root.collapsed && !root.modal ? padding * 2 : padding * 4
482 }
483
484 contentItem: Kirigami.HeaderFooterLayout {
485 id: mainLayout
486
487 anchors {
488 fill: parent
489 topMargin: root.collapsed && !showHeaderWhenCollapsed ? -contentItem.y : 0
490 }
491
492 Behavior on anchors.topMargin {
493 NumberAnimation {
494 duration: Kirigami.Units.longDuration
495 easing.type: Easing.InOutQuad
496 }
497 }
498
499 header: RowLayout {
500 visible: root.title.length > 0 || Boolean(root.titleIcon)
501 spacing: Kirigami.Units.largeSpacing
502
503 Kirigami.Icon {
504 source: root.titleIcon
505 }
506
507 Kirigami.Heading {
508 text: root.title
509 elide: Text.ElideRight
510 visible: !root.collapsed
511 Layout.fillWidth: true
512 }
513 }
514
515 contentItem: QQC2.ScrollView {
516 id: scrollView
517
518 //ensure the attached property exists
519 Kirigami.Theme.inherit: true
520
521 // HACK: workaround for https://bugreports.qt.io/browse/QTBUG-83890
522 QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
523
524 implicitWidth: Math.min(Kirigami.Units.gridUnit * 20, root.parent.width * 0.8)
525
526 Flickable {
527 id: mainFlickable
528
529 contentWidth: width
530 contentHeight: mainColumn.Layout.minimumHeight
531
532 clip: (mainLayout.header?.visible ?? false) || (mainLayout.footer?.visible ?? false)
533
534 ColumnLayout {
535 id: mainColumn
536 width: mainFlickable.width
537 spacing: 0
538 height: Math.max(scrollView.height, Layout.minimumHeight)
539
540 ColumnLayout {
541 id: topContent
542
543 spacing: 0
544
545 Layout.alignment: Qt.AlignHCenter
546 Layout.leftMargin: root.leftPadding
547 Layout.rightMargin: root.rightPadding
548 Layout.bottomMargin: Kirigami.Units.smallSpacing
549 Layout.topMargin: root.topPadding
550 Layout.fillWidth: true
551 Layout.fillHeight: true
552 Layout.preferredHeight: implicitHeight * opacity
553 // NOTE: why this? just Layout.fillWidth: true doesn't seem sufficient
554 // as items are added only after this column creation
555 Layout.minimumWidth: parent.width - root.leftPadding - root.rightPadding
556
557 visible: children.length > 0 && childrenRect.height > 0 && opacity > 0
558 opacity: !root.collapsed || showTopContentWhenCollapsed
559
560 Behavior on opacity {
561 // not an animator as is binded
562 NumberAnimation {
563 duration: Kirigami.Units.longDuration
564 easing.type: Easing.InOutQuad
565 }
566 }
567 }
568
569 T.StackView {
570 id: stackView
571
572 property KP.ActionsMenu openSubMenu
573
574 clip: true
575 Layout.fillWidth: true
576 Layout.minimumHeight: currentItem ? currentItem.implicitHeight : 0
577 Layout.maximumHeight: Layout.minimumHeight
578
579 initialItem: menuComponent
580
581 // NOTE: it's important those are NumberAnimation and not XAnimators
582 // as while the animation is running the drawer may close, and
583 // the animator would stop when not drawing see BUG 381576
584 popEnter: Transition {
585 NumberAnimation { property: "x"; from: (stackView.mirrored ? -1 : 1) * -stackView.width; to: 0; duration: Kirigami.Units.veryLongDuration; easing.type: Easing.OutCubic }
586 }
587
588 popExit: Transition {
589 NumberAnimation { property: "x"; from: 0; to: (stackView.mirrored ? -1 : 1) * stackView.width; duration: Kirigami.Units.veryLongDuration; easing.type: Easing.OutCubic }
590 }
591
592 pushEnter: Transition {
593 NumberAnimation { property: "x"; from: (stackView.mirrored ? -1 : 1) * stackView.width; to: 0; duration: Kirigami.Units.veryLongDuration; easing.type: Easing.OutCubic }
594 }
595
596 pushExit: Transition {
597 NumberAnimation { property: "x"; from: 0; to: (stackView.mirrored ? -1 : 1) * -stackView.width; duration: Kirigami.Units.veryLongDuration; easing.type: Easing.OutCubic }
598 }
599
600 replaceEnter: Transition {
601 NumberAnimation { property: "x"; from: (stackView.mirrored ? -1 : 1) * stackView.width; to: 0; duration: Kirigami.Units.veryLongDuration; easing.type: Easing.OutCubic }
602 }
603
604 replaceExit: Transition {
605 NumberAnimation { property: "x"; from: 0; to: (stackView.mirrored ? -1 : 1) * -stackView.width; duration: Kirigami.Units.veryLongDuration; easing.type: Easing.OutCubic }
606 }
607 }
608
609 Item {
610 Layout.fillWidth: true
611 Layout.fillHeight: root.actions.length > 0
612 Layout.minimumHeight: Kirigami.Units.smallSpacing
613 }
614
615 ColumnLayout {
616 id: mainContent
617 Layout.alignment: Qt.AlignHCenter
618 Layout.leftMargin: root.leftPadding
619 Layout.rightMargin: root.rightPadding
620 Layout.fillWidth: true
621 Layout.fillHeight: true
622 // NOTE: why this? just Layout.fillWidth: true doesn't seem sufficient
623 // as items are added only after this column creation
624 Layout.minimumWidth: parent.width - root.leftPadding - root.rightPadding
625 visible: children.length > 0 && (opacity > 0 || mainContentAnimator.running)
626 opacity: !root.collapsed || showContentWhenCollapsed
627 Behavior on opacity {
628 OpacityAnimator {
629 id: mainContentAnimator
630 duration: Kirigami.Units.longDuration
631 easing.type: Easing.InOutQuad
632 }
633 }
634 }
635
636 Item {
637 Layout.minimumWidth: Kirigami.Units.smallSpacing
638 Layout.minimumHeight: root.bottomPadding
639 }
640
641 QQC2.ToolButton {
642 Layout.fillWidth: true
643
644 icon.name: {
645 if (root.collapsible && root.collapseButtonVisible) {
646 // Check for edge regardless of RTL/locale/mirrored status,
647 // because edge can be set externally.
648 const mirrored = root.edge === Qt.RightEdge;
649
650 if (root.collapsed) {
651 return mirrored ? "sidebar-expand-right" : "sidebar-expand-left";
652 } else {
653 return mirrored ? "sidebar-collapse-right" : "sidebar-collapse-left";
654 }
655 }
656 return "";
657 }
658
659 visible: root.collapsible && root.collapseButtonVisible
660 text: root.collapsed ? "" : qsTr("Close Sidebar")
661
662 onClicked: root.collapsed = !root.collapsed
663
664 QQC2.ToolTip.visible: root.collapsed && (Kirigami.Settings.tabletMode ? pressed : hovered)
665 QQC2.ToolTip.text: qsTr("Open Sidebar")
666 QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
667 }
668 }
669 }
670 }
671 }
672}
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,...
Class for rendering an icon in UI.
Definition icon.h:35
QStringView level(QStringView ifopt)
VehicleSection::Type type(QStringView coachNumber, QStringView coachClassification)
QString name(StandardAction id)
AlignHCenter
ElideRight
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Jan 24 2025 11:51:21 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.