From 6357b949b3d2b72032d5008b670bc6f0202d4f14 Mon Sep 17 00:00:00 2001 From: tux Date: Fri, 4 Sep 2026 22:55:22 +0530 Subject: [PATCH] feat(bar): add smooth sliding highlight to workspaces --- modules/bar/Workspaces.qml | 158 ++++++++++++++++++++++++++++--------- 1 file changed, 121 insertions(+), 37 deletions(-) diff --git a/modules/bar/Workspaces.qml b/modules/bar/Workspaces.qml index 8f468ed..33491a0 100644 --- a/modules/bar/Workspaces.qml +++ b/modules/bar/Workspaces.qml @@ -4,57 +4,141 @@ import QtQuick import Quickshell.Hyprland import qs.config -Row { +Item { id: root property int count: 7 - spacing: Theme.spacing + implicitWidth: pillsRow.implicitWidth + implicitHeight: Theme.workspaces.height + Behavior on implicitWidth { + NumberAnimation { + duration: Theme.duration + easing.type: Easing.OutQuint + } + } - Repeater { - model: root.count + Row { + id: pillsRow - Rectangle { - id: pill + anchors.fill: parent + spacing: Theme.spacing - required property int index - property var ws: Hyprland.workspaces.values.find(w => w.id === pill.index + 1) - property bool isActive: Hyprland.focusedWorkspace?.id === (pill.index + 1) + Repeater { + model: root.count - radius: Theme.workspaces.radius - implicitHeight: Theme.workspaces.height - implicitWidth: pill.isActive ? pill.implicitHeight * Theme.workspaces.activeWidthRatio : pill.implicitHeight - color: { - if (handler.hovered) - return Theme.colors.accent; - if (pill.isActive || pill.ws) - return Theme.colors.accent; - return Theme.colors.inActive; - } + Rectangle { + id: pill - MouseArea { - hoverEnabled: true - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: Hyprland.dispatch("workspace " + (pill.index + 1)) - } + required property int index + property var ws: Hyprland.workspaces.values.find(w => w.id === pill.index + 1) + property bool isActive: Hyprland.focusedWorkspace?.id === (pill.index + 1) - HoverHandler { - id: handler - } - - Behavior on color { - ColorAnimation { - duration: Theme.duration + z: 1 + radius: Theme.workspaces.radius + implicitHeight: Theme.workspaces.height + implicitWidth: pill.isActive ? pill.implicitHeight * Theme.workspaces.activeWidthRatio : pill.implicitHeight + color: { + if (pill.isActive) + return "transparent"; + if (handler.hovered) + return Theme.colors.accent; + if (pill.ws) + return Theme.colors.secondaryForeground; + return Theme.colors.inActive; } - } - Behavior on implicitWidth { - NumberAnimation { - duration: Theme.duration - easing.type: Easing.OutQuad + MouseArea { + hoverEnabled: true + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: Hyprland.dispatch("hl.dsp.focus({ workspace = " + (pill.index + 1) + " })") + } + + HoverHandler { + id: handler + } + + Behavior on color { + ColorAnimation { + duration: Theme.duration + 50 + } + } + + Behavior on implicitWidth { + NumberAnimation { + duration: Theme.duration + easing.type: Easing.OutQuad + } } } } } + + Rectangle { + id: highlight + + z: 2 + radius: Theme.workspaces.radius + color: Theme.colors.accent + + readonly property real activeW: Theme.workspaces.height * Theme.workspaces.activeWidthRatio + readonly property real inactiveW: Theme.workspaces.height + property int curIdx: Hyprland.focusedWorkspace ? Hyprland.focusedWorkspace.id - 1 : -1 + property int prevIdx: 0 + + function getX(index, activeIndex) { + if (index < 0) + return 0; + let x = 0; + for (let i = 0; i < index; i++) + x += (i === activeIndex ? activeW : inactiveW) + pillsRow.spacing; + return x; + } + + property real targetLeft: curIdx >= 0 ? getX(curIdx, curIdx) : 0 + property real targetRight: curIdx >= 0 ? targetLeft + activeW : 0 + property real actualLeft: targetLeft + property real actualRight: targetRight + + Behavior on actualLeft { + NumberAnimation { + id: leftAnim + duration: Theme.duration + easing.type: Easing.OutQuint + } + } + + Behavior on actualRight { + NumberAnimation { + id: rightAnim + duration: Theme.duration + easing.type: Easing.OutQuint + } + } + + onCurIdxChanged: { + if (curIdx >= 0 && prevIdx >= 0) { + if (curIdx > prevIdx) { + leftAnim.duration = 400; + rightAnim.duration = 300; + } else if (curIdx < prevIdx) { + leftAnim.duration = 300; + rightAnim.duration = 400; + } + } + if (curIdx >= 0) + prevIdx = curIdx; + } + + x: actualLeft + width: actualRight - actualLeft + height: parent.height + opacity: curIdx >= 0 ? 1 : 0 + Behavior on opacity { + NumberAnimation { + duration: 180 + } + } + } }