summaryrefslogtreecommitdiff
path: root/modules/dashboard/Tabs.qml
diff options
context:
space:
mode:
author2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-05-17 00:44:00 +0800
committer2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-05-17 00:44:00 +0800
commit6e6db08726e3bc274594de6835a01fc10002def9 (patch)
tree272dcaa27b96690c449cdf61c8e4e6ea21559f48 /modules/dashboard/Tabs.qml
parentlauncher: fallback app icon (diff)
downloadcaelestia-shell-6e6db08726e3bc274594de6835a01fc10002def9.tar.gz
caelestia-shell-6e6db08726e3bc274594de6835a01fc10002def9.tar.bz2
caelestia-shell-6e6db08726e3bc274594de6835a01fc10002def9.zip
feat: dashboard
Diffstat (limited to 'modules/dashboard/Tabs.qml')
-rw-r--r--modules/dashboard/Tabs.qml226
1 files changed, 226 insertions, 0 deletions
diff --git a/modules/dashboard/Tabs.qml b/modules/dashboard/Tabs.qml
new file mode 100644
index 0000000..49f836b
--- /dev/null
+++ b/modules/dashboard/Tabs.qml
@@ -0,0 +1,226 @@
+import "root:/widgets"
+import "root:/services"
+import "root:/config"
+import Quickshell.Widgets
+import QtQuick
+import QtQuick.Controls
+
+Item {
+ id: root
+
+ property alias currentIndex: bar.currentIndex
+
+ implicitHeight: bar.height + indicator.height + indicator.anchors.topMargin + separator.height
+
+ TabBar {
+ id: bar
+
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.top: parent.top
+
+ background: null
+
+ Tab {
+ iconName: "dashboard"
+ text: qsTr("Dashboard")
+ }
+
+ Tab {
+ iconName: "queue_music"
+ text: qsTr("Media")
+ }
+
+ Tab {
+ iconName: "speed"
+ text: qsTr("Performance")
+ }
+
+ Tab {
+ iconName: "workspaces"
+ text: qsTr("Workspaces")
+ }
+ }
+
+ StyledRect {
+ id: indicator
+
+ anchors.top: bar.bottom
+ anchors.topMargin: DashboardConfig.sizes.tabIndicatorSpacing
+
+ implicitWidth: bar.currentItem.implicitWidth
+ implicitHeight: DashboardConfig.sizes.tabIndicatorHeight
+
+ x: {
+ const tab = bar.currentItem;
+ return tab.x + (tab.width - tab.implicitWidth) / 2;
+ }
+
+ color: Colours.palette.m3primary
+ radius: Appearance.rounding.full
+
+ Behavior on x {
+ Anim {}
+ }
+
+ Behavior on implicitWidth {
+ Anim {}
+ }
+ }
+
+ StyledRect {
+ id: separator
+
+ anchors.top: indicator.bottom
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ implicitHeight: 1
+ color: Colours.palette.m3outlineVariant
+ }
+
+ component Tab: TabButton {
+ id: tab
+
+ required property string iconName
+ readonly property bool current: TabBar.tabBar.currentItem === this
+
+ background: null
+
+ contentItem: MouseArea {
+ id: mouse
+
+ implicitWidth: Math.max(icon.width, label.width)
+ implicitHeight: icon.height + label.height
+
+ cursorShape: Qt.PointingHandCursor
+
+ onPressed: ({x,y}) => {
+ tab.TabBar.tabBar.setCurrentIndex(tab.TabBar.index);
+
+ const stateY = stateWrapper.y;
+ rippleAnim.x = x;
+ rippleAnim.y = y - stateY;
+
+ const dist = (ox,oy) => ox*ox + oy*oy
+ const stateEndY = stateY + stateWrapper.height
+ rippleAnim.radius = Math.sqrt(Math.max(dist(0, stateY), dist(0, stateEndY), dist(width, stateY), dist(width, stateEndY)))
+
+ rippleAnim.restart();
+ }
+ onWheel: event => {
+ if (event.angleDelta.y > 0)
+ tab.TabBar.tabBar.incrementCurrentIndex();
+ else if (event.angleDelta.y < 0)
+ tab.TabBar.tabBar.decrementCurrentIndex();
+ }
+
+ 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.1
+ }
+ ParallelAnimation {
+ Anim {
+ target: ripple
+ properties: "implicitWidth,implicitHeight"
+ from: 0
+ to: rippleAnim.radius * 2
+ duration: Appearance.anim.durations.large
+ easing.bezierCurve: Appearance.anim.curves.standardDecel
+ }
+ Anim {
+ target: ripple
+ property: "opacity"
+ to: 0
+ duration: Appearance.anim.durations.large
+ easing.type: Easing.BezierSpline
+ easing.bezierCurve: Appearance.anim.curves.standardDecel
+ }
+ }
+ }
+
+ ClippingRectangle {
+ id: stateWrapper
+
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ implicitHeight: parent.height + DashboardConfig.sizes.tabIndicatorSpacing * 2
+
+ color: "transparent"
+ radius: Appearance.rounding.small
+
+ StyledRect {
+ id: stateLayer
+
+ anchors.fill: parent
+
+ color: tab.current ? Colours.palette.m3primary : Colours.palette.m3onSurface
+ opacity: mouse.pressed ? 0.1 : tab.hovered ? 0.08 : 0
+
+ Behavior on opacity {
+ Anim {}
+ }
+ }
+
+ StyledRect {
+ id: ripple
+
+ radius: Appearance.rounding.full
+ color: tab.current ? Colours.palette.m3primary : Colours.palette.m3onSurface
+ 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.current ? Colours.palette.m3primary : Colours.palette.m3onSurfaceVariant
+ font.pointSize: Appearance.font.size.large
+ }
+
+ StyledText {
+ id: label
+
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.bottom: parent.bottom
+
+ text: tab.text
+ color: tab.current ? Colours.palette.m3primary : Colours.palette.m3onSurfaceVariant
+ }
+ }
+ }
+
+ component Anim: NumberAnimation {
+ duration: Appearance.anim.durations.normal
+ easing.type: Easing.BezierSpline
+ easing.bezierCurve: Appearance.anim.curves.standard
+ }
+}