feat: dashboard

This commit is contained in:
2 * r + 2 * t 2025-05-17 00:44:00 +08:00
parent 25e6f17c60
commit 6e6db08726
9 changed files with 430 additions and 1 deletions

View file

@ -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
}
}

View file

@ -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
}
}
}

View file

@ -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
}
}

226
modules/dashboard/Tabs.qml Normal file
View file

@ -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
}
}

View file

@ -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
}
}
}

View file

@ -4,6 +4,7 @@ import "root:/modules/osd" as Osd
import "root:/modules/notifications" as Notifications import "root:/modules/notifications" as Notifications
import "root:/modules/session" as Session import "root:/modules/session" as Session
import "root:/modules/launcher" as Launcher import "root:/modules/launcher" as Launcher
import "root:/modules/dashboard" as Dashboard
import QtQuick.Shapes import QtQuick.Shapes
Shape { Shape {
@ -43,4 +44,11 @@ Shape {
startX: (root.width - wrapper.width) / 2 startX: (root.width - wrapper.width) / 2
startY: root.height startY: root.height
} }
Dashboard.Background {
wrapper: panels.dashboard
startX: (root.width - wrapper.width) / 2
startY: 0
}
} }

View file

@ -89,10 +89,11 @@ Variants {
PersistentProperties { PersistentProperties {
id: visibilities id: visibilities
property bool launcher
property bool osd property bool osd
property bool notifications: Notifs.popups.length > 0 property bool notifications: Notifs.popups.length > 0
property bool session property bool session
property bool launcher
property bool dashboard
Component.onCompleted: Visibilities.screens[scope.modelData] = this Component.onCompleted: Visibilities.screens[scope.modelData] = this
} }

View file

@ -21,6 +21,10 @@ MouseArea {
function inRightPanel(panel: Item, x: real, y: real): bool { function inRightPanel(panel: Item, x: real, y: real): bool {
return x > BorderConfig.thickness + panel.x && withinPanelHeight(panel, x, y); 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 anchors.fill: parent
@ -50,6 +54,9 @@ MouseArea {
else if (dragX > SessionConfig.dragThreshold) else if (dragX > SessionConfig.dragThreshold)
root.visibilities.session = false; root.visibilities.session = false;
} }
const showDashboard = root.inTopPanel(panels.dashboard, x, y);
root.visibilities.dashboard = showDashboard;
} }
} }

View file

@ -3,6 +3,7 @@ import "root:/modules/osd" as Osd
import "root:/modules/notifications" as Notifications import "root:/modules/notifications" as Notifications
import "root:/modules/session" as Session import "root:/modules/session" as Session
import "root:/modules/launcher" as Launcher import "root:/modules/launcher" as Launcher
import "root:/modules/dashboard" as Dashboard
import Quickshell import Quickshell
import QtQuick import QtQuick
@ -16,6 +17,7 @@ Item {
readonly property Notifications.Wrapper notifications: notifications readonly property Notifications.Wrapper notifications: notifications
readonly property Session.Wrapper session: session readonly property Session.Wrapper session: session
readonly property Launcher.Wrapper launcher: launcher readonly property Launcher.Wrapper launcher: launcher
readonly property Dashboard.Wrapper dashboard: dashboard
anchors.fill: parent anchors.fill: parent
anchors.margins: BorderConfig.thickness anchors.margins: BorderConfig.thickness
@ -58,4 +60,13 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
} }
Dashboard.Wrapper {
id: dashboard
visibilities: root.visibilities
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
}
} }