diff options
| author | 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> | 2025-05-17 00:44:00 +0800 |
|---|---|---|
| committer | 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> | 2025-05-17 00:44:00 +0800 |
| commit | 6e6db08726e3bc274594de6835a01fc10002def9 (patch) | |
| tree | 272dcaa27b96690c449cdf61c8e4e6ea21559f48 | |
| parent | launcher: fallback app icon (diff) | |
| download | caelestia-shell-6e6db08726e3bc274594de6835a01fc10002def9.tar.gz caelestia-shell-6e6db08726e3bc274594de6835a01fc10002def9.tar.bz2 caelestia-shell-6e6db08726e3bc274594de6835a01fc10002def9.zip | |
feat: dashboard
| -rw-r--r-- | config/DashboardConfig.qml | 13 | ||||
| -rw-r--r-- | modules/dashboard/Background.qml | 71 | ||||
| -rw-r--r-- | modules/dashboard/Content.qml | 34 | ||||
| -rw-r--r-- | modules/dashboard/Tabs.qml | 226 | ||||
| -rw-r--r-- | modules/dashboard/Wrapper.qml | 58 | ||||
| -rw-r--r-- | modules/drawers/Backgrounds.qml | 8 | ||||
| -rw-r--r-- | modules/drawers/Drawers.qml | 3 | ||||
| -rw-r--r-- | modules/drawers/Interactions.qml | 7 | ||||
| -rw-r--r-- | modules/drawers/Panels.qml | 11 |
9 files changed, 430 insertions, 1 deletions
diff --git a/config/DashboardConfig.qml b/config/DashboardConfig.qml new file mode 100644 index 0000000..a0b2106 --- /dev/null +++ b/config/DashboardConfig.qml @@ -0,0 +1,13 @@ +pragma Singleton + +import Quickshell +import QtQuick + +Singleton { + readonly property Sizes sizes: Sizes {} + + component Sizes: QtObject { + readonly property int tabIndicatorHeight: 3 + readonly property int tabIndicatorSpacing: 5 + } +} diff --git a/modules/dashboard/Background.qml b/modules/dashboard/Background.qml new file mode 100644 index 0000000..0ab6658 --- /dev/null +++ b/modules/dashboard/Background.qml @@ -0,0 +1,71 @@ +import QtQuick +import QtQuick.Shapes +import "root:/config" +import "root:/services" + +ShapePath { + id: root + + required property Wrapper wrapper + readonly property real rounding: BorderConfig.rounding + readonly property bool flatten: wrapper.height < rounding * 2 + readonly property real roundingY: flatten ? wrapper.height / 2 : rounding + + strokeWidth: -1 + fillColor: BorderConfig.colour + + PathArc { + relativeX: root.rounding + relativeY: root.roundingY + radiusX: root.rounding + radiusY: Math.min(root.rounding, root.wrapper.height) + } + + PathLine { + relativeX: 0 + relativeY: root.wrapper.height - root.roundingY * 2 + } + + PathArc { + relativeX: root.rounding + relativeY: root.roundingY + radiusX: root.rounding + radiusY: Math.min(root.rounding, root.wrapper.height) + direction: PathArc.Counterclockwise + } + + PathLine { + relativeX: root.wrapper.width - root.rounding * 4 + relativeY: 0 + } + + PathArc { + relativeX: root.rounding + relativeY: -root.roundingY + radiusX: root.rounding + radiusY: Math.min(root.rounding, root.wrapper.height) + direction: PathArc.Counterclockwise + } + + PathLine { + relativeX: 0 + relativeY: -(root.wrapper.height - root.roundingY * 2) + } + + PathArc { + relativeX: root.rounding + relativeY: -root.roundingY + radiusX: root.rounding + radiusY: Math.min(root.rounding, root.wrapper.height) + } + + Behavior on fillColor { + ColorAnimation { + duration: Appearance.anim.durations.normal + easing.type: Easing.BezierSpline + easing.bezierCurve: Appearance.anim.curves.standard + } + + } + +} diff --git a/modules/dashboard/Content.qml b/modules/dashboard/Content.qml new file mode 100644 index 0000000..f3f102a --- /dev/null +++ b/modules/dashboard/Content.qml @@ -0,0 +1,34 @@ +import "root:/widgets" +import "root:/services" +import "root:/config" +import Quickshell +import QtQuick +import QtQuick.Controls + +Item { + id: root + + required property PersistentProperties visibilities + + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + + implicitWidth: 800 + implicitHeight: 300 + + Tabs { + id: tabs + + anchors.fill: parent + anchors.topMargin: Appearance.padding.normal + anchors.margins: Appearance.padding.large + + currentIndex: view.currentIndex + } + + SwipeView { + id: view + + currentIndex: tabs.currentIndex + } +} 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 + } +} diff --git a/modules/dashboard/Wrapper.qml b/modules/dashboard/Wrapper.qml new file mode 100644 index 0000000..c4388e6 --- /dev/null +++ b/modules/dashboard/Wrapper.qml @@ -0,0 +1,58 @@ +import QtQuick +import Quickshell +import "root:/config" + +Item { + id: root + + required property PersistentProperties visibilities + + visible: height > 0 + implicitHeight: 0 + implicitWidth: content.width + BorderConfig.rounding * 2 + transitions: [ + Transition { + from: "" + to: "visible" + + NumberAnimation { + target: root + property: "implicitHeight" + duration: Appearance.anim.durations.large + easing.type: Easing.BezierSpline + easing.bezierCurve: Appearance.anim.curves.emphasizedDecel + } + + }, + Transition { + from: "visible" + to: "" + + NumberAnimation { + target: root + property: "implicitHeight" + duration: Appearance.anim.durations.normal + easing.type: Easing.BezierSpline + easing.bezierCurve: Appearance.anim.curves.emphasizedAccel + } + + } + ] + + Content { + id: content + + visibilities: root.visibilities + } + + states: State { + name: "visible" + when: root.visibilities.dashboard + + PropertyChanges { + root.implicitHeight: content.height + } + + } + +} diff --git a/modules/drawers/Backgrounds.qml b/modules/drawers/Backgrounds.qml index 3e0156f..a78c667 100644 --- a/modules/drawers/Backgrounds.qml +++ b/modules/drawers/Backgrounds.qml @@ -4,6 +4,7 @@ import "root:/modules/osd" as Osd import "root:/modules/notifications" as Notifications import "root:/modules/session" as Session import "root:/modules/launcher" as Launcher +import "root:/modules/dashboard" as Dashboard import QtQuick.Shapes Shape { @@ -43,4 +44,11 @@ Shape { startX: (root.width - wrapper.width) / 2 startY: root.height } + + Dashboard.Background { + wrapper: panels.dashboard + + startX: (root.width - wrapper.width) / 2 + startY: 0 + } } diff --git a/modules/drawers/Drawers.qml b/modules/drawers/Drawers.qml index 51374e9..91c6fc8 100644 --- a/modules/drawers/Drawers.qml +++ b/modules/drawers/Drawers.qml @@ -89,10 +89,11 @@ Variants { PersistentProperties { id: visibilities - property bool launcher property bool osd property bool notifications: Notifs.popups.length > 0 property bool session + property bool launcher + property bool dashboard Component.onCompleted: Visibilities.screens[scope.modelData] = this } diff --git a/modules/drawers/Interactions.qml b/modules/drawers/Interactions.qml index e937326..0dfda50 100644 --- a/modules/drawers/Interactions.qml +++ b/modules/drawers/Interactions.qml @@ -21,6 +21,10 @@ MouseArea { function inRightPanel(panel: Item, x: real, y: real): bool { return x > BorderConfig.thickness + panel.x && withinPanelHeight(panel, x, y); } + + function inTopPanel(panel: Item, x: real, y: real): bool { + const panelX = BorderConfig.thickness + panel.x; + return y < BorderConfig.thickness + panel.y + panel.height && x >= panelX && x <= panelX + panel.width; } anchors.fill: parent @@ -50,6 +54,9 @@ MouseArea { else if (dragX > SessionConfig.dragThreshold) root.visibilities.session = false; } + + const showDashboard = root.inTopPanel(panels.dashboard, x, y); + root.visibilities.dashboard = showDashboard; } } diff --git a/modules/drawers/Panels.qml b/modules/drawers/Panels.qml index ab5f8de..9bde4b2 100644 --- a/modules/drawers/Panels.qml +++ b/modules/drawers/Panels.qml @@ -3,6 +3,7 @@ import "root:/modules/osd" as Osd import "root:/modules/notifications" as Notifications import "root:/modules/session" as Session import "root:/modules/launcher" as Launcher +import "root:/modules/dashboard" as Dashboard import Quickshell import QtQuick @@ -16,6 +17,7 @@ Item { readonly property Notifications.Wrapper notifications: notifications readonly property Session.Wrapper session: session readonly property Launcher.Wrapper launcher: launcher + readonly property Dashboard.Wrapper dashboard: dashboard anchors.fill: parent anchors.margins: BorderConfig.thickness @@ -58,4 +60,13 @@ Item { anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.bottom } + + Dashboard.Wrapper { + id: dashboard + + visibilities: root.visibilities + + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + } } |