Warning, /frameworks/kirigami/src/controls/NavigationTabBar.qml is written in an unsupported language. File is not indexed.
0001 /*
0002 * Copyright 2021 Devin Lin <espidev@gmail.com>
0003 *
0004 * SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006
0007 pragma ComponentBehavior: Bound
0008
0009 import QtQuick
0010 import QtQml
0011 import QtQuick.Layouts
0012 import QtQuick.Controls as QQC2
0013 import QtQuick.Templates as T
0014 import org.kde.kirigami as Kirigami
0015
0016 /**
0017 * @brief Page navigation tab-bar, used as an alternative to sidebars for 3-5 elements.
0018 *
0019 * Can be combined with secondary toolbars above (if in the footer) to provide page actions.
0020 *
0021 * Example usage:
0022 * @code{.qml}
0023 * import QtQuick 2.15
0024 * import QtQuick.Controls 2.15
0025 * import QtQuick.Layouts 1.15
0026 * import org.kde.kirigami 2.20 as Kirigami
0027 *
0028 * Kirigami.ApplicationWindow {
0029 * title: "Clock"
0030 *
0031 * pageStack.initialPage: worldPage
0032 *
0033 * Kirigami.Page {
0034 * id: worldPage
0035 * title: "World"
0036 * visible: false
0037 * }
0038 * Kirigami.Page {
0039 * id: timersPage
0040 * title: "Timers"
0041 * visible: false
0042 * }
0043 * Kirigami.Page {
0044 * id: stopwatchPage
0045 * title: "Stopwatch"
0046 * visible: false
0047 * }
0048 * Kirigami.Page {
0049 * id: alarmsPage
0050 * title: "Alarms"
0051 * visible: false
0052 * }
0053 *
0054 * footer: Kirigami.NavigationTabBar {
0055 * actions: [
0056 * Kirigami.Action {
0057 * icon.name: "globe"
0058 * text: "World"
0059 * checked: worldPage.visible
0060 * onTriggered: {
0061 * if (!worldPage.visible) {
0062 * while (pageStack.depth > 0) {
0063 * pageStack.pop();
0064 * }
0065 * pageStack.push(worldPage);
0066 * }
0067 * }
0068 * },
0069 * Kirigami.Action {
0070 * icon.name: "player-time"
0071 * text: "Timers"
0072 * checked: timersPage.visible
0073 * onTriggered: {
0074 * if (!timersPage.visible) {
0075 * while (pageStack.depth > 0) {
0076 * pageStack.pop();
0077 * }
0078 * pageStack.push(timersPage);
0079 * }
0080 * }
0081 * },
0082 * Kirigami.Action {
0083 * icon.name: "chronometer"
0084 * text: "Stopwatch"
0085 * checked: stopwatchPage.visible
0086 * onTriggered: {
0087 * if (!stopwatchPage.visible) {
0088 * while (pageStack.depth > 0) {
0089 * pageStack.pop();
0090 * }
0091 * pageStack.push(stopwatchPage);
0092 * }
0093 * }
0094 * },
0095 * Kirigami.Action {
0096 * icon.name: "notifications"
0097 * text: "Alarms"
0098 * checked: alarmsPage.visible
0099 * onTriggered: {
0100 * if (!alarmsPage.visible) {
0101 * while (pageStack.depth > 0) {
0102 * pageStack.pop();
0103 * }
0104 * pageStack.push(alarmsPage);
0105 * }
0106 * }
0107 * }
0108 * ]
0109 * }
0110 * }
0111 * @endcode
0112 *
0113 * @see NavigationTabButton
0114 * @since 5.87
0115 * @since org.kde.kirigami 2.19
0116 * @inherit QtQuick.Templates.Toolbar
0117 */
0118
0119 QQC2.ToolBar {
0120 id: root
0121
0122 //BEGIN properties
0123 /**
0124 * @brief This property holds the list of actions to be displayed in the toolbar.
0125 */
0126 property list<T.Action> actions
0127
0128 /**
0129 * @brief This property holds a subset of visible actions of the list of actions.
0130 *
0131 * An action is considered visible if it is either a Kirigami.Action with
0132 * ``visible`` property set to true, or it is a plain QQC2.Action.
0133 */
0134 readonly property list<T.Action> visibleActions: actions
0135 // Note: instanceof check implies `!== null`
0136 .filter(action => action instanceof Kirigami.Action
0137 ? action.visible
0138 : action !== null
0139 )
0140
0141 /**
0142 * @brief The property holds the maximum width of the toolbar actions, before margins are added.
0143 */
0144 property real maximumContentWidth: {
0145 const minDelegateWidth = Kirigami.Units.gridUnit * 5;
0146 // Always have at least the width of 5 items, so that small amounts of actions look natural.
0147 return minDelegateWidth * Math.max(visibleActions.length, 5);
0148 }
0149
0150 /**
0151 * @brief This property holds the index of currently checked tab.
0152 *
0153 * If the index set is out of bounds, or the triggered signal did not change any checked property of an action, the index
0154 * will remain the same.
0155 */
0156 property int currentIndex: tabGroup.checkedButton && tabGroup.buttons.length > 0 ? tabGroup.checkedButton.tabIndex : -1
0157
0158 /**
0159 * @brief This property holds the number of tab buttons.
0160 */
0161 readonly property int count: tabGroup.buttons.length
0162
0163 /**
0164 * @brief This property holds the ButtonGroup used to manage the tabs.
0165 */
0166 readonly property T.ButtonGroup tabGroup: tabGroup
0167
0168 /**
0169 * @brief This property holds the calculated width that buttons on the tab bar use.
0170 *
0171 * @since 5.102
0172 */
0173 property real buttonWidth: {
0174 // Counting buttons because Repeaters can be counted among visibleChildren
0175 let visibleButtonCount = 0;
0176 const minWidth = contentItem.height * 0.75;
0177 for (const visibleChild of contentItem.visibleChildren) {
0178 if (contentItem.width / visibleButtonCount >= minWidth && // make buttons go off the screen if there is physically no room for them
0179 visibleChild instanceof T.AbstractButton) { // Checking for AbstractButtons because any AbstractButton can act as a tab
0180 ++visibleButtonCount;
0181 }
0182 }
0183
0184 return Math.round(contentItem.width / visibleButtonCount);
0185 }
0186 //END properties
0187
0188 onCurrentIndexChanged: {
0189 if (currentIndex === -1) {
0190 if (tabGroup.checkState !== Qt.Unchecked) {
0191 tabGroup.checkState = Qt.Unchecked;
0192 }
0193 return;
0194 }
0195 if (!tabGroup.checkedButton || tabGroup.checkedButton.tabIndex !== currentIndex) {
0196 const buttonForCurrentIndex = tabGroup.buttons[currentIndex]
0197 if (buttonForCurrentIndex.action) {
0198 // trigger also toggles and causes clicked() to be emitted
0199 buttonForCurrentIndex.action.trigger();
0200 } else {
0201 // toggle() does not trigger the action,
0202 // so don't use it if you want to use an action.
0203 // It also doesn't cause clicked() to be emitted.
0204 buttonForCurrentIndex.toggle();
0205 }
0206 }
0207 }
0208
0209 // ensure that by default, we do not have unintended padding and spacing from the style
0210 spacing: 0
0211 padding: 0
0212 topPadding: undefined
0213 leftPadding: undefined
0214 rightPadding: undefined
0215 bottomPadding: undefined
0216 verticalPadding: undefined
0217 // Using Math.round() on horizontalPadding can cause the contentItem to jitter left and right when resizing the window.
0218 horizontalPadding: Math.floor(Math.max(0, width - root.maximumContentWidth) / 2)
0219
0220 contentWidth: Math.ceil(Math.min(root.availableWidth, root.maximumContentWidth))
0221 implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, contentWidth + leftPadding + rightPadding)
0222 implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, contentHeight + topPadding + bottomPadding)
0223 position: {
0224 if (QQC2.ApplicationWindow.window?.footer === root) {
0225 return QQC2.ToolBar.Footer
0226 } else if (parent?.footer === root) {
0227 return QQC2.ToolBar.Footer
0228 } else if (parent?.parent?.footer === parent) {
0229 return QQC2.ToolBar.Footer
0230 } else {
0231 return QQC2.ToolBar.Header
0232 }
0233 }
0234
0235 // Using Row because setting just width is more convenient than having to set Layout.minimumWidth and Layout.maximumWidth
0236 contentItem: Row {
0237 id: rowLayout
0238 spacing: root.spacing
0239 }
0240
0241 // Used to manage which tab is checked and change the currentIndex
0242 T.ButtonGroup {
0243 id: tabGroup
0244 exclusive: true
0245 buttons: root.contentItem.children
0246
0247 onCheckedButtonChanged: {
0248 if (!checkedButton) {
0249 return
0250 }
0251 if (root.currentIndex !== checkedButton.tabIndex) {
0252 root.currentIndex = checkedButton.tabIndex;
0253 }
0254 }
0255 }
0256
0257 // Using a Repeater here because Instantiator was causing issues:
0258 // NavigationTabButtons that were supposed to be destroyed were still
0259 // registered as buttons in tabGroup.
0260 // NOTE: This will make Repeater show up as child through visibleChildren
0261 Repeater {
0262 id: instantiator
0263 model: root.visibleActions
0264 delegate: NavigationTabButton {
0265 id: delegate
0266
0267 required property T.Action modelData
0268
0269 parent: root.contentItem
0270 action: modelData
0271 width: root.buttonWidth
0272 // Workaround setting the action when checkable is not explicitly set making tabs uncheckable
0273 onActionChanged: action.checkable = true
0274
0275 Kirigami.Theme.textColor: root.Kirigami.Theme.textColor
0276 Kirigami.Theme.backgroundColor: root.Kirigami.Theme.backgroundColor
0277 Kirigami.Theme.highlightColor: root.Kirigami.Theme.highlightColor
0278 }
0279 }
0280 }