quickshell-toki-night/modules/dashboard/Tabs.qml

269 lines
7.4 KiB
QML

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