pragma ComponentBehavior: Bound import QtQuick import QtQuick.Layouts import QtQuick.Templates import Quickshell import ZShell.Config import qs.Components import qs.Services Item { id: root required property real nonAnimWidth required property ScreenState screenState required property var tabs readonly property alias count: bar.count readonly property int tabIndicatorSpacing: Tokens.spacing.small implicitHeight: bar.implicitHeight + bar.anchors.topMargin + indicator.implicitHeight + indicator.anchors.topMargin + separator.implicitHeight TabBar { id: bar anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top anchors.topMargin: root.tabIndicatorSpacing currentIndex: root.screenState.dashboardTab onCurrentIndexChanged: root.screenState.dashboardTab = currentIndex implicitHeight: contentHeight background: null contentItem: RowLayout { spacing: 0 Repeater { model: bar.contentModel } } Repeater { model: ScriptModel { values: root.tabs } delegate: Tab { required property var modelData iconName: modelData.iconName text: modelData.text } } } Item { id: indicator anchors.top: bar.bottom anchors.topMargin: root.tabIndicatorSpacing implicitWidth: { const tab = bar.currentItem; if (tab) return tab.implicitWidth; const width = (root.nonAnimWidth - bar.spacing * (bar.count - 1)) / bar.count; return width; } implicitHeight: 3 x: { const tab = bar.currentItem; const width = (root.nonAnimWidth - bar.spacing * (bar.count - 1)) / bar.count; const tabWidth = tab?.implicitWidth ?? width; return width * bar.currentIndex + (width - tabWidth) / 2; } clip: true CustomRect { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right implicitHeight: parent.implicitHeight * 2 color: Colors.palette.m3primary radius: Tokens.rounding.full } Behavior on x { Anim {} } Behavior on implicitWidth { Anim {} } } CustomRect { id: separator anchors.top: indicator.bottom anchors.left: parent.left anchors.right: parent.right implicitHeight: 1 color: Colors.palette.m3outlineVariant } component Tab: TabButton { id: tab required property string iconName readonly property bool current: TabBar.tabBar.currentItem === this Layout.fillWidth: true Layout.preferredWidth: 1 // Uniform width across all tabs implicitWidth: implicitContentWidth implicitHeight: implicitContentHeight background: null contentItem: CustomMouseArea { id: mouse function onWheel(event: WheelEvent): void { if (event.angleDelta.y < 0) root.screenState.dashboardTab = Math.min(root.screenState.dashboardTab + 1, bar.count - 1); else if (event.angleDelta.y > 0) root.screenState.dashboardTab = Math.max(root.screenState.dashboardTab - 1, 0); } implicitWidth: Math.max(icon.width, label.width) implicitHeight: icon.height + label.height hoverEnabled: true cursorShape: Qt.PointingHandCursor onPressed: root.screenState.dashboardTab = tab.TabBar.index StateLayer { id: stateLayer anchors.fill: undefined anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter implicitHeight: parent.height + root.tabIndicatorSpacing * 2 radius: Tokens.rounding.medium color: tab.current ? Colors.palette.m3primary : Colors.palette.m3onSurface onClicked: root.screenState.dashboardTab = tab.TabBar.index } MaterialIcon { id: icon anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: label.top text: tab.iconName color: tab.current ? Colors.palette.m3primary : Colors.palette.m3onSurfaceVariant fill: tab.current ? 1 : 0 fontStyle: Tokens.font.icon.medium Behavior on fill { Anim { type: Anim.DefaultEffects } } } CustomText { id: label anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.bottom text: tab.text color: tab.current ? Colors.palette.m3primary : Colors.palette.m3onSurfaceVariant } } } }