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

KDE's Doxygen guidelines are available online.