import "root:/" import "root:/services/" import "root:/modules/common" import "root:/modules/common/widgets" import "root:/modules/common/functions/color_utils.js" as ColorUtils import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Wayland import Quickshell.Hyprland import Quickshell.Io import Quickshell.Widgets import Qt5Compat.GraphicalEffects Item { required property var bar property bool borderless: ConfigOptions.bar.borderless readonly property HyprlandMonitor monitor: Hyprland.monitorFor(bar.screen) readonly property Toplevel activeWindow: ToplevelManager.activeToplevel readonly property int workspaceGroup: Math.floor((monitor.activeWorkspace?.id - 1) / ConfigOptions.bar.workspaces.shown) property list workspaceOccupied: [] property int widgetPadding: 4 property int workspaceButtonWidth: 26 property real workspaceIconSize: workspaceButtonWidth * 0.69 property real workspaceIconSizeShrinked: workspaceButtonWidth * 0.55 property real workspaceIconOpacityShrinked: 1 property real workspaceIconMarginShrinked: -4 property int workspaceIndexInGroup: (monitor.activeWorkspace?.id - 1) % ConfigOptions.bar.workspaces.shown // Function to update workspaceOccupied function updateWorkspaceOccupied() { workspaceOccupied = Array.from({ length: ConfigOptions.bar.workspaces.shown }, (_, i) => { return Hyprland.workspaces.values.some(ws => ws.id === workspaceGroup * ConfigOptions.bar.workspaces.shown + 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(); } } implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2 implicitHeight: 40 // 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 } MouseArea { anchors.fill: parent acceptedButtons: Qt.BackButton onPressed: (event) => { if (event.button === Qt.BackButton) { Hyprland.dispatch(`togglespecialworkspace`); } } } // Workspaces - background RowLayout { id: rowLayout z: 1 spacing: 0 anchors.fill: parent implicitHeight: 40 Repeater { model: ConfigOptions.bar.workspaces.shown Rectangle { z: 1 implicitWidth: workspaceButtonWidth implicitHeight: workspaceButtonWidth radius: Appearance.rounding.full property var leftOccupied: (workspaceOccupied[index-1] && !(!activeWindow?.activated && monitor.activeWorkspace?.id === index)) property var rightOccupied: (workspaceOccupied[index+1] && !(!activeWindow?.activated && monitor.activeWorkspace?.id === index+2)) property var radiusLeft: leftOccupied ? 0 : Appearance.rounding.full property var radiusRight: rightOccupied ? 0 : Appearance.rounding.full topLeftRadius: radiusLeft bottomLeftRadius: radiusLeft topRightRadius: radiusRight bottomRightRadius: radiusRight color: ColorUtils.transparentize(Appearance.m3colors.m3secondaryContainer, 0.4) opacity: (workspaceOccupied[index] && !(!activeWindow?.activated && monitor.activeWorkspace?.id === index+1)) ? 1 : 0 Behavior on opacity { animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } Behavior on radiusLeft { animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } Behavior on radiusRight { animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } } } } // Active workspace Rectangle { z: 2 // Make active ws indicator, which has a brighter color, smaller to look like it is of the same size as ws occupied highlight property real activeWorkspaceMargin: 2 implicitHeight: workspaceButtonWidth - activeWorkspaceMargin * 2 radius: Appearance.rounding.full color: Appearance.colors.colPrimary anchors.verticalCenter: parent.verticalCenter property real idx1: workspaceIndexInGroup property real idx2: workspaceIndexInGroup x: Math.min(idx1, idx2) * workspaceButtonWidth + activeWorkspaceMargin implicitWidth: Math.abs(idx1 - idx2) * workspaceButtonWidth + workspaceButtonWidth - activeWorkspaceMargin * 2 Behavior on activeWorkspaceMargin { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } Behavior on idx1 { // Leading anim NumberAnimation { duration: 100 easing.type: Easing.OutSine } } Behavior on idx2 { // Following anim NumberAnimation { duration: 300 easing.type: Easing.OutSine } } } // Workspaces - numbers RowLayout { id: rowLayoutNumbers z: 3 spacing: 0 anchors.fill: parent implicitHeight: 40 Repeater { model: ConfigOptions.bar.workspaces.shown Button { id: button property int workspaceValue: workspaceGroup * ConfigOptions.bar.workspaces.shown + index + 1 Layout.fillHeight: true onPressed: Hyprland.dispatch(`workspace ${workspaceValue}`) width: workspaceButtonWidth background: Item { id: workspaceButtonBackground implicitWidth: workspaceButtonWidth implicitHeight: workspaceButtonWidth property var biggestWindow: { const windowsInThisWorkspace = HyprlandData.windowList.filter(w => w.workspace.id == button.workspaceValue) return windowsInThisWorkspace.reduce((maxWin, win) => { const maxArea = (maxWin?.size?.[0] ?? 0) * (maxWin?.size?.[1] ?? 0) const winArea = (win?.size?.[0] ?? 0) * (win?.size?.[1] ?? 0) return winArea > maxArea ? win : maxWin }, null) } property var mainAppIconSource: Quickshell.iconPath(AppSearch.guessIcon(biggestWindow?.class), "image-missing") StyledText { // Workspace number text opacity: GlobalStates.workspaceShowNumbers || ((ConfigOptions?.bar.workspaces.alwaysShowNumbers && (!ConfigOptions?.bar.workspaces.showAppIcons || !workspaceButtonBackground.biggestWindow || GlobalStates.workspaceShowNumbers)) || (GlobalStates.workspaceShowNumbers && !ConfigOptions?.bar.workspaces.showAppIcons) ) ? 1 : 0 z: 3 anchors.centerIn: parent horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter font.pixelSize: Appearance.font.pixelSize.small - ((text.length - 1) * (text !== "10") * 2) text: `${button.workspaceValue}` elide: Text.ElideRight color: (monitor.activeWorkspace?.id == button.workspaceValue) ? Appearance.m3colors.m3onPrimary : (workspaceOccupied[index] ? Appearance.m3colors.m3onSecondaryContainer : Appearance.colors.colOnLayer1Inactive) Behavior on opacity { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } } Rectangle { // Dot instead of ws number opacity: (ConfigOptions?.bar.workspaces.alwaysShowNumbers || GlobalStates.workspaceShowNumbers || (ConfigOptions?.bar.workspaces.showAppIcons && workspaceButtonBackground.biggestWindow) ) ? 0 : 1 visible: opacity > 0 anchors.centerIn: parent width: workspaceButtonWidth * 0.18 height: width radius: width / 2 color: (monitor.activeWorkspace?.id == button.workspaceValue) ? Appearance.m3colors.m3onPrimary : (workspaceOccupied[index] ? Appearance.m3colors.m3onSecondaryContainer : Appearance.colors.colOnLayer1Inactive) Behavior on opacity { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } } Item { // Main app icon anchors.centerIn: parent width: workspaceButtonWidth height: workspaceButtonWidth opacity: !ConfigOptions?.bar.workspaces.showAppIcons ? 0 : (workspaceButtonBackground.biggestWindow && !GlobalStates.workspaceShowNumbers && ConfigOptions?.bar.workspaces.showAppIcons) ? 1 : workspaceButtonBackground.biggestWindow ? workspaceIconOpacityShrinked : 0 visible: opacity > 0 IconImage { id: mainAppIcon anchors.bottom: parent.bottom anchors.right: parent.right anchors.bottomMargin: (!GlobalStates.workspaceShowNumbers && ConfigOptions?.bar.workspaces.showAppIcons) ? (workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked anchors.rightMargin: (!GlobalStates.workspaceShowNumbers && ConfigOptions?.bar.workspaces.showAppIcons) ? (workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked source: workspaceButtonBackground.mainAppIconSource implicitSize: (!GlobalStates.workspaceShowNumbers && ConfigOptions?.bar.workspaces.showAppIcons) ? workspaceIconSize : workspaceIconSizeShrinked Behavior on opacity { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } Behavior on anchors.bottomMargin { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } Behavior on anchors.rightMargin { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } Behavior on implicitSize { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } } } } } } } }