qs: better sidebar tab switching animation

This commit is contained in:
2026-03-06 15:26:07 +01:00
parent f9facdd61b
commit 883eec3e4d
5 changed files with 276 additions and 216 deletions
@@ -0,0 +1,121 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Components
import qs.Constants
Rectangle {
id: root
property string currentValue: ""
property string firstOptionValue: "1"
property string firstOptionIcon: "1"
property string secondOptionValue: "2"
property string secondOptionIcon: "2"
signal toggled(string value)
implicitWidth: Style.baseWidgetSize * 2.8
implicitHeight: Style.baseWidgetSize
radius: Math.min(Style.radiusS, height / 2)
color: Colors.mSurface
Row {
anchors.fill: parent
spacing: Style.marginS / 2
Rectangle {
width: root.currentValue === root.firstOptionValue ? (parent.width - parent.spacing) * 0.65 : (parent.width - parent.spacing) * 0.35
height: parent.height
radius: Math.min(Style.radiusS, height / 2)
color: root.currentValue === root.firstOptionValue ? Colors.mPrimary : "transparent"
UIcon {
anchors.centerIn: parent
iconName: root.firstOptionIcon
iconSize: Style.fontSizeL
color: root.currentValue === root.firstOptionValue ? Colors.mOnPrimary : Colors.mOnSurface
Behavior on color {
ColorAnimation {
duration: 200
}
}
}
MouseArea {
anchors.fill: parent
onClicked: {
root.toggled(root.firstOptionValue);
}
cursorShape: Qt.PointingHandCursor
}
Behavior on width {
NumberAnimation {
duration: 250
easing.type: Easing.OutCubic
}
}
Behavior on color {
ColorAnimation {
duration: 200
}
}
}
Rectangle {
width: root.currentValue === root.secondOptionValue ? (parent.width - parent.spacing) * 0.65 : (parent.width - parent.spacing) * 0.35
height: parent.height
radius: Math.min(Style.radiusS, height / 2)
color: root.currentValue === root.secondOptionValue ? Colors.mPrimary : "transparent"
UIcon {
anchors.centerIn: parent
iconName: root.secondOptionIcon
iconSize: Style.fontSizeL
color: root.currentValue === root.secondOptionValue ? Colors.mOnPrimary : Colors.mOnSurface
Behavior on color {
ColorAnimation {
duration: 200
}
}
}
MouseArea {
anchors.fill: parent
onClicked: {
root.toggled(root.secondOptionValue);
}
cursorShape: Qt.PointingHandCursor
}
Behavior on width {
NumberAnimation {
duration: 250
easing.type: Easing.OutCubic
}
}
Behavior on color {
ColorAnimation {
duration: 200
}
}
}
}
}
@@ -10,7 +10,7 @@ UBox {
property string currentPanel: ShellState.leftSiderbarTab // "bluetooth", "wifi" property string currentPanel: ShellState.leftSiderbarTab // "bluetooth", "wifi"
implicitHeight: contentLoader.implicitHeight + toggleGroup.implicitHeight + Style.marginXS * 2 + Style.marginS * 2 implicitHeight: (root.currentPanel === "bluetooth" ? btContentLoader.implicitHeight : wifiContentLoader.implicitHeight) + toggleGroup.implicitHeight + Style.marginXS * 2 + Style.marginS * 2
ColumnLayout { ColumnLayout {
spacing: Style.marginXS spacing: Style.marginXS
@@ -20,122 +20,62 @@ UBox {
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
Rectangle { UDualIconToggle {
// border.color: Colors.mOutline
id: toggleGroup id: toggleGroup
Layout.preferredWidth: Style.baseWidgetSize * 2.8 Layout.preferredWidth: Style.baseWidgetSize * 2.8
Layout.preferredHeight: Style.baseWidgetSize Layout.preferredHeight: Style.baseWidgetSize
radius: Math.min(Style.radiusS, height / 2) firstOptionValue: "bluetooth"
color: Colors.mSurface firstOptionIcon: "bluetooth"
secondOptionValue: "wifi"
Row { secondOptionIcon: "wifi"
anchors.fill: parent currentValue: root.currentPanel
spacing: Style.marginS / 2 onToggled: (value) => {
ShellState.leftSiderbarTab = value;
Rectangle {
id: btnBluetooth
width: root.currentPanel === "bluetooth" ? (parent.width - parent.spacing) * 0.65 : (parent.width - parent.spacing) * 0.35
height: parent.height
radius: Math.min(Style.radiusS, height / 2)
color: root.currentPanel === "bluetooth" ? Colors.mPrimary : "transparent"
UIcon {
anchors.centerIn: parent
iconName: "bluetooth"
iconSize: Style.fontSizeL
color: root.currentPanel === "bluetooth" ? Colors.mOnPrimary : Colors.mOnSurface
Behavior on color {
ColorAnimation {
duration: 200
} }
}
}
MouseArea {
anchors.fill: parent
onClicked: ShellState.leftSiderbarTab = "bluetooth"
cursorShape: Qt.PointingHandCursor
}
Behavior on width {
NumberAnimation {
duration: 250
easing.type: Easing.OutCubic
}
}
Behavior on color {
ColorAnimation {
duration: 200
}
}
}
Rectangle {
id: btnWifi
width: root.currentPanel === "wifi" ? (parent.width - parent.spacing) * 0.65 : (parent.width - parent.spacing) * 0.35
height: parent.height
radius: Math.min(Style.radiusS, height / 2)
color: root.currentPanel === "wifi" ? Colors.mPrimary : "transparent"
UIcon {
anchors.centerIn: parent
iconName: "wifi"
iconSize: Style.fontSizeL
color: root.currentPanel === "wifi" ? Colors.mOnPrimary : Colors.mOnSurface
Behavior on color {
ColorAnimation {
duration: 200
}
}
}
MouseArea {
anchors.fill: parent
onClicked: ShellState.leftSiderbarTab = "wifi"
cursorShape: Qt.PointingHandCursor
}
Behavior on width {
NumberAnimation {
duration: 250
easing.type: Easing.OutCubic
}
}
Behavior on color {
ColorAnimation {
duration: 200
}
}
}
}
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
Item {
implicitWidth: Math.max(btHeaderLoader.implicitWidth, wfHeaderLoader.implicitWidth)
implicitHeight: Math.max(btHeaderLoader.implicitHeight, wfHeaderLoader.implicitHeight)
Loader { Loader {
sourceComponent: currentPanel === "bluetooth" ? bluetoothHeaderComponent : wifiHeaderComponent id: btHeaderLoader
anchors.right: parent.right
sourceComponent: bluetoothHeaderComponent
opacity: root.currentPanel === "bluetooth" ? 1 : 0
visible: opacity > 0
Behavior on opacity {
NumberAnimation {
duration: 300
}
}
}
Loader {
id: wfHeaderLoader
anchors.right: parent.right
sourceComponent: wifiHeaderComponent
opacity: root.currentPanel === "wifi" ? 1 : 0
visible: opacity > 0
Behavior on opacity {
NumberAnimation {
duration: 300
}
}
}
Component { Component {
id: bluetoothHeaderComponent id: bluetoothHeaderComponent
@@ -201,12 +141,66 @@ UBox {
Layout.fillWidth: true Layout.fillWidth: true
} }
Loader { Item {
id: contentLoader id: contentContainer
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
sourceComponent: currentPanel === "bluetooth" ? bluetoothComponent : wifiComponent clip: true
Loader {
id: btContentLoader
width: parent.width
height: parent.height
x: root.currentPanel === "bluetooth" ? 0 : -width
opacity: root.currentPanel === "bluetooth" ? 1 : 0
sourceComponent: bluetoothComponent
Behavior on x {
NumberAnimation {
duration: 300
easing.type: Easing.OutCubic
}
}
Behavior on opacity {
NumberAnimation {
duration: 300
easing.type: Easing.OutCubic
}
}
}
Loader {
id: wifiContentLoader
width: parent.width
height: parent.height
x: root.currentPanel === "wifi" ? 0 : width
opacity: root.currentPanel === "wifi" ? 1 : 0
sourceComponent: wifiComponent
Behavior on x {
NumberAnimation {
duration: 300
easing.type: Easing.OutCubic
}
}
Behavior on opacity {
NumberAnimation {
duration: 300
easing.type: Easing.OutCubic
}
}
}
Component { Component {
id: bluetoothComponent id: bluetoothComponent
@@ -14,127 +14,73 @@ Item {
anchors.fill: parent anchors.fill: parent
spacing: Style.marginM spacing: Style.marginM
Rectangle { UDualIconToggle {
id: toggleGroup id: toggleGroup
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: Style.baseWidgetSize * 2.8 Layout.preferredWidth: Style.baseWidgetSize * 2.8
Layout.preferredHeight: Style.baseWidgetSize Layout.preferredHeight: Style.baseWidgetSize
radius: Math.min(Style.radiusS, height / 2) firstOptionValue: "notifications"
color: Colors.mSurface firstOptionIcon: "bell"
secondOptionValue: "notes"
Row { secondOptionIcon: "notes"
anchors.fill: parent currentValue: root.currentPanel
spacing: Style.marginS / 2 onToggled: (value) => {
ShellState.rightSiderbarTab = value;
Rectangle {
id: btnNotifications
width: root.currentPanel === "notifications" ? (parent.width - parent.spacing) * 0.65 : (parent.width - parent.spacing) * 0.35
height: parent.height
radius: Math.min(Style.radiusS, height / 2)
color: root.currentPanel === "notifications" ? Colors.mPrimary : "transparent"
UIcon {
anchors.centerIn: parent
iconName: "bell"
iconSize: Style.fontSizeL
color: root.currentPanel === "notifications" ? Colors.mOnPrimary : Colors.mOnSurface
Behavior on color {
ColorAnimation {
duration: 200
} }
}
}
MouseArea {
anchors.fill: parent
onClicked: ShellState.rightSiderbarTab = "notifications"
cursorShape: Qt.PointingHandCursor
}
Behavior on width {
NumberAnimation {
duration: 250
easing.type: Easing.OutCubic
}
}
Behavior on color {
ColorAnimation {
duration: 200
}
}
}
Rectangle {
id: btnNotes
width: root.currentPanel === "notes" ? (parent.width - parent.spacing) * 0.65 : (parent.width - parent.spacing) * 0.35
height: parent.height
radius: Math.min(Style.radiusS, height / 2)
color: root.currentPanel === "notes" ? Colors.mPrimary : "transparent"
UIcon {
anchors.centerIn: parent
iconName: "notes"
iconSize: Style.fontSizeL
color: root.currentPanel === "notes" ? Colors.mOnPrimary : Colors.mOnSurface
Behavior on color {
ColorAnimation {
duration: 200
}
}
}
MouseArea {
anchors.fill: parent
onClicked: ShellState.rightSiderbarTab = "notes"
cursorShape: Qt.PointingHandCursor
}
Behavior on width {
NumberAnimation {
duration: 250
easing.type: Easing.OutCubic
}
}
Behavior on color {
ColorAnimation {
duration: 200
}
}
}
}
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
clip: true
NotificationHistoryCard { NotificationHistoryCard {
anchors.fill: parent width: parent.width
visible: root.currentPanel === "notifications" height: parent.height
x: root.currentPanel === "notifications" ? 0 : -width
opacity: root.currentPanel === "notifications" ? 1 : 0
Behavior on x {
NumberAnimation {
duration: 300
easing.type: Easing.OutCubic
}
}
Behavior on opacity {
NumberAnimation {
duration: 300
easing.type: Easing.OutCubic
}
}
} }
NoteCard { NoteCard {
anchors.fill: parent width: parent.width
visible: root.currentPanel === "notes" height: parent.height
x: root.currentPanel === "notes" ? 0 : width
opacity: root.currentPanel === "notes" ? 1 : 0
Behavior on x {
NumberAnimation {
duration: 300
easing.type: Easing.OutCubic
}
}
Behavior on opacity {
NumberAnimation {
duration: 300
easing.type: Easing.OutCubic
}
}
} }
} }
@@ -124,7 +124,7 @@ Singleton {
Timer { Timer {
id: fetchIPDebouncer id: fetchIPDebouncer
interval: 1000 interval: 5000
repeat: false repeat: false
running: false running: false
onTriggered: { onTriggered: {
@@ -186,7 +186,6 @@ Singleton {
if (newPlayer !== currentPlayer) { if (newPlayer !== currentPlayer) {
currentPlayer = newPlayer; currentPlayer = newPlayer;
currentPosition = currentPlayer ? currentPlayer.position : 0; currentPosition = currentPlayer ? currentPlayer.position : 0;
Logger.d("Media", "Switching player");
} }
} }