import QtQuick import QtQuick.Layouts import Quickshell.Hyprland import Qt5Compat.GraphicalEffects import qs.config import qs.components Item { id: root implicitWidth: layout.implicitWidth property var ws: Hyprland.workspaces property var toplevels: Hyprland.toplevels.values // update activeOccupied when toplevels changes as getWorkspaceStat's return is non reactive property int activeIndex: -1 property bool activeOccupied: false property bool toChild: false function getWorkspaceStats(index) { const w = ws.values.find(i => i.id === index + 1); return { isOccupied: w?.toplevels?.values?.length, isActive: w?.active, isUrgent: w?.urgent }; } onToplevelsChanged: { Qt.callLater(() => { activeOccupied = getWorkspaceStats(activeIndex).isOccupied || false; }); } DropShadow { anchors.fill: activeRect source: activeRect radius: 8 cached: true color: root.activeOccupied ? Colors.accent : Colors.foreground1 opacity: root.activeOccupied ? Bar.wsActiveOpacity : Bar.wsEmptyOpacity / 3 Behavior on color { ColorAnimation { duration: Bar.wsAnimationDuration easing.type: Easing.InOutQuad } } Behavior on opacity { NumberAnimation { duration: Bar.wsAnimationDuration easing.type: Easing.InOutQuad } } } IRect { id: activeRect x: layout.x + root.activeIndex * Bar.wsIconSize + root.activeIndex * Bar.wsSpacing anchors.verticalCenter: layout.verticalCenter implicitWidth: Bar.wsActiveIconSize implicitHeight: Bar.wsIconSize - Bar.wsHorizontalSpacing radius: Bar.wsRounding color: root.activeOccupied ? Colors.accent : Colors.foreground1 opacity: root.activeOccupied ? Bar.wsActiveOpacity : Bar.wsEmptyOpacity / 3 Behavior on x { ISpringAnimation {} } Behavior on color { ColorAnimation { duration: Bar.wsAnimationDuration easing.type: Easing.InOutQuad } } Behavior on opacity { NumberAnimation { duration: Bar.wsAnimationDuration easing.type: Easing.InOutQuad } } Behavior on width { NumberAnimation { duration: Bar.wsAnimationDuration easing.type: Easing.InOutQuad } } } RowLayout { id: layout anchors.verticalCenter: parent.verticalCenter implicitWidth: repeater.width spacing: Bar.wsSpacing Repeater { id: repeater model: Bar.wss IRect { property var st: getWorkspaceStats(index) property bool active: st?.isActive || false implicitWidth: active ? Bar.wsActiveIconSize : Bar.wsIconSize implicitHeight: Bar.wsIconSize - Bar.wsHorizontalSpacing radius: Bar.wsRounding color: st.isOccupied ? Colors.accent : Colors.foreground1 opacity: active && st.isOccupied ? Bar.wsActiveOpacity : st.isOccupied ? Bar.wsOpacity : Bar.wsEmptyOpacity Icon { anchors.centerIn: parent text: !st.isUrgent ? Icons.ws[index + 1] : Icons.ws['urgent'] font.pixelSize: !active ? Bar.wsIconSize / 2 : Bar.wsActiveIconSize / 2.5 opacity: !st.isOccupied ? 0 : 1 color: !st.isUrgent ? !st.isActive ? Colors.foreground2 : Colors.foreground1 : Colors.red Behavior on font.pixelSize { ISpringAnimation {} } Behavior on color { ColorAnimation { duration: Bar.wsAnimationDuration easing.type: Easing.InOutQuad } } } MouseArea { anchors { fill: parent topMargin: Bar.wsExtraMouseArea * -1 bottomMargin: Bar.wsExtraMouseArea * -1 } acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton hoverEnabled: true onPressed: { if (!parent.active) Hyprland.dispatch(`hl.dsp.focus({ workspace = '${index + 1}' })`); } function moveActive() { activeRect.x = parent.x; root.activeOccupied = st.isOccupied || false; activeRect.implicitWidth = active ? Bar.wsActiveIconSize : Bar.wsIconSize; } onEntered: { root.toChild = true; moveActive(); } // https://github.com/quickshell-mirror/quickshell/issues/118 // onEntered: { // moveActive(); // } onExited: { root.toChild = false; // outer MouseArea will handle this // if (!active && previousActiveIndex >= 0) { // // just in case // activeRect.x = layout.x + previousActiveIndex * Bar.wsIconSize + previousActiveIndex * Bar.wsSpacing; // activeRect.implicitWidth = Bar.wsActiveIconSize; // // root.activeOccupied = parent.st.isOccupied || false; // root.activeOccupied = getWorkspaceStats(previousActiveIndex).isOccupied || false; // previousActiveIndex = -1; // } } onWheel: event => { // no idea wats wrong w/ prev so we just unify it to use +/- 1 if (event.angleDelta.y < 0) { if (activeIndex + 1 < Bar.wss) Hyprland.dispatch(`hl.dsp.focus({ workspace = '+1' })`); else Hyprland.dispatch(`hl.dsp.focus({ workspace = '1' })`); } else if (event.angleDelta.y > 0) { if (activeIndex + 1 > 1) { Hyprland.dispatch(`hl.dsp.focus({ workspace = '-1' })`); } else if (Bar.wss > 1) { Hyprland.dispatch(`hl.dsp.focus({ workspace = '${Bar.wss}' })`); } } } } Behavior on opacity { NumberAnimation { duration: Bar.wsAnimationDuration easing.type: Easing.InOutQuad } } Behavior on implicitWidth { NumberAnimation { duration: Bar.wsAnimationDuration easing.type: Easing.InOutQuad } } Behavior on color { ColorAnimation { duration: Bar.wsAnimationDuration easing.type: Easing.InOutQuad } } onActiveChanged: if (active) { root.activeIndex = index; root.activeOccupied = st.isOccupied; activeRect.implicitWidth = Bar.wsActiveIconSize; } } } } MouseArea { height: Bar.height width: parent.width hoverEnabled: true z: -1000 // to be below the child's MouseArea acceptedButtons: Qt.MiddleButton // sync w the inner MouseArea // https://github.com/quickshell-mirror/quickshell/issues/118 // but onEnter won't update the mouseX onEntered: { const abovedItemIndex = Math.round((mouseX - layout.x) / (Bar.wsIconSize + Bar.wsSpacing)) - 1; // TODO: workspaceActiveIconSize might be before activeRect.x = mouseX - activeRect.width / 2; root.activeOccupied = root.getWorkspaceStats(abovedItemIndex).isOccupied || false; activeRect.implicitWidth = root.activeIndex === abovedItemIndex ? Bar.wsActiveIconSize : Bar.wsIconSize; } onExited: { if (!root.toChild && root.activeIndex >= 0) { // just in case activeRect.x = layout.x + root.activeIndex * Bar.wsIconSize + root.activeIndex * Bar.wsSpacing; activeRect.implicitWidth = Bar.wsActiveIconSize; root.activeOccupied = root.getWorkspaceStats(root.activeIndex).isOccupied || false; } } onWheel: event => { // no idea wats wrong w/ prev so we just unify it to use +/- 1 if (event.angleDelta.y < 0) { if (root.activeIndex + 1 < Bar.wss) Hyprland.dispatch(`hl.dsp.focus({ workspace = '+1' })`); else Hyprland.dispatch(`hl.dsp.focus({ workspace = '1' })`); } else if (event.angleDelta.y > 0) { if (root.activeIndex + 1 > 1) Hyprland.dispatch(`hl.dsp.focus({ workspace = '1' })`); else if (Bar.wss > 1) Hyprland.dispatch(`hl.dsp.focus({ workspace = '${Bar.wss}' })`); } } } }