From ff93725b28f57b9c9df20e5decb2a4f685789dad Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 10 Apr 2025 17:03:57 +0200 Subject: [PATCH] the perfect workspace indicator --- .config/quickshell/modules/bar/Bar.qml | 11 + .config/quickshell/modules/bar/Workspaces.qml | 198 ++++++++++++++++++ .../quickshell/modules/common/Appearance.qml | 4 + .../modules/common/ConfigOptions.qml | 10 + .../common/widgets/SmallCircleButton.qml | 1 - 5 files changed, 223 insertions(+), 1 deletion(-) create mode 100644 .config/quickshell/modules/bar/Workspaces.qml create mode 100644 .config/quickshell/modules/common/ConfigOptions.qml diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index f8d92db9b..acbc8a0b3 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -9,6 +9,7 @@ Scope { model: Quickshell.screens PanelWindow { + id: barRoot property var modelData screen: modelData @@ -24,8 +25,10 @@ Scope { RowLayout { anchors.centerIn: parent implicitWidth: 500 + spacing: 8 // TODO: Why is this halved when rendered?? RowLayout { + spacing: 4 Layout.fillWidth: true Layout.fillHeight: true } @@ -33,15 +36,23 @@ Scope { RowLayout { Layout.fillWidth: true Layout.fillHeight: true + spacing: 4 + + Workspaces { + bar: barRoot + } + } RowLayout { Layout.fillWidth: true Layout.fillHeight: true + spacing: 4 ClockWidget { Layout.alignment: Qt.AlignVCenter } + UtilButtons { Layout.alignment: Qt.AlignVCenter } diff --git a/.config/quickshell/modules/bar/Workspaces.qml b/.config/quickshell/modules/bar/Workspaces.qml new file mode 100644 index 000000000..1f7c0dab3 --- /dev/null +++ b/.config/quickshell/modules/bar/Workspaces.qml @@ -0,0 +1,198 @@ +import "../common" +import "../common/widgets" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Hyprland +import Quickshell.Io + +Rectangle { + required property var bar + readonly property HyprlandMonitor monitor: Hyprland.monitorFor(bar.screen) + readonly property list workspaceOccupied: [] + readonly property int workspaceGroup: Math.floor((monitor.activeWorkspace?.id - 1) / ConfigOptions.bar.workspacesShown) + property int widgetPadding: 4 + property int workspaceButtonWidth: 26 + property int activeWorkspaceMargin: 1 + property double animatedActiveWorkspaceIndex: (monitor.activeWorkspace?.id - 1) % ConfigOptions.bar.workspacesShown + + Behavior on animatedActiveWorkspaceIndex { + NumberAnimation { + duration: Appearance.animation.menuDecel.duration + easing.type: Appearance.animation.menuDecel.type + } + + } + + // Function to update workspaceOccupied + function updateWorkspaceOccupied() { + workspaceOccupied = Array.from({ length: ConfigOptions.bar.workspacesShown }, (_, i) => { + return Hyprland.workspaces.values.some(ws => ws.id === workspaceGroup * ConfigOptions.bar.workspacesShown + i + 1); + }); + } + + // Initialize workspaceOccupied when the component is created + Component.onCompleted: updateWorkspaceOccupied() + + // Listen for changes in Hyprland.workspaces.values + Connections { + target: Hyprland.workspaces + function onValuesChanged() { + updateWorkspaceOccupied(); + } + } + + + Layout.fillHeight: true + implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2 + implicitHeight: 40 + color: "transparent" + + // Background + Rectangle { + z: 0 + anchors.centerIn: parent + implicitHeight: 32 + implicitWidth: rowLayout.implicitWidth + widgetPadding * 2 + radius: Appearance.rounding.small + color: Appearance.colors.colLayer1 + } + + // Scroll to switch workspaces + WheelHandler { + onWheel: (event) => { + if (event.angleDelta.y < 0) + Hyprland.dispatch(`workspace r+1`); + else if (event.angleDelta.y > 0) + Hyprland.dispatch(`workspace r-1`); + } + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + } + + // Workspaces - background + RowLayout { + id: rowLayout + z: 1 + + spacing: 0 + anchors.fill: parent + implicitHeight: 40 + + Repeater { + model: ConfigOptions.bar.workspacesShown + + Rectangle { + z: 1 + implicitWidth: workspaceButtonWidth + implicitHeight: workspaceButtonWidth + radius: Appearance.rounding.full + property var radiusLeft: workspaceOccupied[index-1] ? 0 : Appearance.rounding.full + property var radiusRight: workspaceOccupied[index+1] ? 0 : Appearance.rounding.full + + topLeftRadius: radiusLeft + bottomLeftRadius: radiusLeft + topRightRadius: radiusRight + bottomRightRadius: radiusRight + + color: Appearance.colors.colLayer2 + opacity: workspaceOccupied[index] ? 1 : 0 + // color: workspaceOccupied[index] ? Appearance.colors.colLayer2 : "transparent" + + Behavior on opacity { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + easing.bezierCurve: Appearance.animation.elementDecel.bezierCurve + } + } + Behavior on radiusLeft { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + easing.bezierCurve: Appearance.animation.elementDecel.bezierCurve + } + } + + Behavior on radiusRight { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + easing.bezierCurve: Appearance.animation.elementDecel.bezierCurve + } + } + + } + + } + + } + + // Active workspace + Rectangle { + z: 2 + implicitWidth: workspaceButtonWidth - activeWorkspaceMargin * 2 + implicitHeight: workspaceButtonWidth - activeWorkspaceMargin * 2 + radius: Appearance.rounding.full + color: Appearance.m3colors.m3primary + anchors.verticalCenter: parent.verticalCenter + x: animatedActiveWorkspaceIndex * workspaceButtonWidth + activeWorkspaceMargin + } + + // Workspaces - numbers + RowLayout { + id: rowLayoutNumbers + z: 3 + + spacing: 0 + anchors.fill: parent + implicitHeight: 40 + + Repeater { + model: ConfigOptions.bar.workspacesShown + + Button { + id: button + Layout.fillHeight: true + topInset: 7 + bottomInset: 7 + onPressed: Hyprland.dispath(`workspace ${index+1}`) + width: workspaceButtonWidth + + contentItem: StyledText { + z: 3 + property int workspaceValue: workspaceGroup * ConfigOptions.bar.workspacesShown + index + 1 + + anchors.fill: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.pointSize: Appearance.font.pointSize.small + text: `${workspaceValue}` + elide: Text.ElideRight + color: (monitor.activeWorkspace?.id == workspaceValue) ? Appearance.m3colors.m3onPrimary : (workspaceOccupied[index] ? Appearance.colors.colOnLayer1 : Appearance.colors.colOnLayer1Inactive) + + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + easing.bezierCurve: Appearance.animation.elementDecel.bezierCurve + } + + } + + } + + background: Rectangle { + color: "transparent" // Transparent background + implicitWidth: workspaceButtonWidth + implicitHeight: workspaceButtonWidth + } + + + } + + } + + } + +} diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index 155f80dc3..00783c13c 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -145,6 +145,10 @@ Singleton { property int type: Easing.BezierSpline property list bezierCurve: [0, 0.55, 0.45, 1] } + property QtObject menuDecel: QtObject { + property int duration: 250 + property int type: Easing.OutCubic + } } } diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml new file mode 100644 index 000000000..1ff2f2cba --- /dev/null +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -0,0 +1,10 @@ +import QtQuick +import Quickshell +pragma Singleton + +Singleton { + property QtObject bar: QtObject { + property int workspacesShown: 10 + } + +} diff --git a/.config/quickshell/modules/common/widgets/SmallCircleButton.qml b/.config/quickshell/modules/common/widgets/SmallCircleButton.qml index 545887bfa..cd881f38b 100644 --- a/.config/quickshell/modules/common/widgets/SmallCircleButton.qml +++ b/.config/quickshell/modules/common/widgets/SmallCircleButton.qml @@ -4,7 +4,6 @@ import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Io -import Quickshell.Wayland Button { id: button