269 lines
7.4 KiB
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
|
|
}
|
|
}
|
|
}
|