pragma ComponentBehavior: Bound import qs.config import qs.custom import qs.services import Quickshell import Quickshell.Widgets import QtQuick import QtQuick.Layouts Item { id: root required property real nonAnimHeight required property PersistentProperties uiState readonly property int count: repeater.model.count readonly property color color: indicator.currentItem.color implicitWidth: childrenRect.width ColumnLayout { id: bar anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter spacing: 16 width: 100 Repeater { id: repeater model: ListModel {} Component.onCompleted: { model.append({ text: qsTr("Dashboard"), iconName: "dashboard", color: Config.colors.dashboard }); model.append({ text: qsTr("Mixer"), iconName: "tune", color: Config.colors.mixer }); model.append({ text: qsTr("Media"), iconName: "queue_music", color: Config.colors.media }); model.append({ text: qsTr("Performance"), iconName: "speed", color: Config.colors.performance }); model.append({ text: qsTr("Workspaces"), iconName: "workspaces", color: Config.colors.workspaces }); } delegate: Tab {} } } Item { id: indicator anchors.left: bar.right anchors.leftMargin: 8 property int currentIndex: root.uiState.dashboardTab property Item currentItem: { repeater.count; repeater.itemAt(currentIndex) } implicitWidth: 2 implicitHeight: currentItem.implicitHeight y: currentItem ? currentItem.y + bar.y + (currentItem.height - implicitHeight) / 2 : 0 clip: true CustomRect { anchors.left: parent.left anchors.top: parent.top anchors.bottom: parent.bottom implicitWidth: parent.implicitWidth * 2 color: indicator.currentItem?.color ?? "transparent" radius: 1000 } Behavior on currentIndex { SequentialAnimation { Anim { target: indicator property: "implicitHeight" to: 0 duration: Config.anim.durations.small / 2 } PropertyAction {} Anim { target: indicator property: "implicitHeight" from: 0 to: bar.children[root.uiState.dashboardTab].height duration: Config.anim.durations.small / 2 } } } } CustomRect { id: separator anchors.left: indicator.right anchors.top: parent.top anchors.bottom: parent.bottom implicitWidth: 1 color: Config.colors.inactive } component Tab: CustomMouseArea { id: tab required property int index required property string text required property string iconName required property color color readonly property bool isCurrentItem: root.uiState.dashboardTab === index implicitHeight: icon.height + label.height + 8 Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true hoverEnabled: true cursorShape: Qt.PointingHandCursor onPressed: event => { root.uiState.dashboardTab = tab.index; const stateY = stateWrapper.y; rippleAnim.x = event.x; rippleAnim.y = event.y - stateY; const dist = (ox, oy) => ox * ox + oy * oy; rippleAnim.radius = Math.sqrt(Math.max(dist(event.x, event.y + stateY), dist(event.x, stateWrapper.height - event.y), dist(width - event.x, event.y + stateY), dist(width - event.x, stateWrapper.height - event.y))); rippleAnim.restart(); } function onWheel(event: WheelEvent): void { if (event.angleDelta.y < 0) root.uiState.dashboardTab = Math.min(root.uiState.dashboardTab + 1, root.count - 1); else if (event.angleDelta.y > 0) root.uiState.dashboardTab = Math.max(root.uiState.dashboardTab - 1, 0); } SequentialAnimation { id: rippleAnim property real x property real y property real radius PropertyAction { target: ripple property: "x" value: rippleAnim.x } PropertyAction { target: ripple property: "y" value: rippleAnim.y } PropertyAction { target: ripple property: "opacity" value: 0.08 } Anim { target: ripple properties: "implicitWidth,implicitHeight" from: 0 to: rippleAnim.radius * 2 easing.bezierCurve: Config.anim.curves.standardDecel } Anim { target: ripple property: "opacity" to: 0 } } ClippingRectangle { id: stateWrapper anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter implicitHeight: parent.height color: "transparent" radius: 12 CustomRect { id: stateLayer anchors.fill: parent color: tab.isCurrentItem ? tab.color : Config.colors.primary opacity: tab.pressed ? 0.1 : tab.containsMouse ? 0.08 : 0 Behavior on opacity { Anim {} } } CustomRect { id: ripple radius: 1000 color: tab.isCurrentItem ? tab.color : Config.colors.primary opacity: 0 transform: Translate { x: -ripple.width / 2 y: -ripple.height / 2 } } } MaterialIcon { id: icon anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: label.top text: tab.iconName color: tab.isCurrentItem ? tab.color : Config.colors.primary fill: tab.isCurrentItem ? 1 : 0 font.pointSize: Config.font.size.large Behavior on fill { Anim { duration: Config.anim.durations.small } } } CustomText { id: label anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.bottom anchors.bottomMargin: 5 text: tab.text color: tab.isCurrentItem ? tab.color : Config.colors.primary } } }