Files
dotfiles/dot_config/quickshell/bar/Workspaces.qml
s0wlz (Matthias Puchstein) c5f7162ebb quickshell: add initial bar config with per-monitor workspaces
- Vertical bar on DP-2 with rounded-square pills throughout
- Per-monitor workspace groups sorted by screen x position, with
  Nerd Font icons for named workspaces and apex-neon red active indicator
- Bar layout: datetime+weather top, workspaces centered, gamemode+media+notif+system bottom
- Popouts anchor to triggering icon (top-right for datetime/weather, bottom-right for media/notif/system)
- Lock command switched from hyprlock to swaylock
- Hyprland blur/ignore_alpha layerrules for quickshell namespace

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 20:00:54 +02:00

165 lines
5.6 KiB
QML

import Quickshell
import Quickshell.Hyprland
import QtQuick
import "../shared" as Shared
// Rounded-square workspace slots with sliding active highlight.
// Set monitorName to show only workspaces on a specific monitor.
Item {
id: root
property string monitorName: ""
property int wsCount: Shared.Config.workspaceCount
readonly property int cellSize: Shared.Theme.barInnerWidth - Shared.Theme.barPadding * 2
readonly property var iconMap: ({
"mail": "\u{eb1c}",
"comms": "\u{ee59}",
"element": "\u{f1d7}",
"joplin": "\u{f249}",
"steam": "\u{f1b6}",
"spotify": "\u{f1bc}"
})
// { id, name } objects for workspaces on this monitor, sorted by name numerically.
property var monitorWsData: {
if (!monitorName) return [];
let all = Hyprland.workspaces, data = [];
for (let i = 0; i < all.values.length; i++) {
let ws = all.values[i];
if (ws.monitor && ws.monitor.name === monitorName)
data.push({ id: ws.id, name: ws.name });
}
data.sort((a, b) => parseInt(a.name) - parseInt(b.name));
return data;
}
// Active workspace ID for this monitor specifically
property int activeWsId: {
if (monitorName) {
let mons = Hyprland.monitors;
for (let i = 0; i < mons.values.length; i++) {
if (mons.values[i].name === monitorName)
return mons.values[i].activeWorkspace?.id ?? -1;
}
return -1;
}
let fw = Hyprland.focusedWorkspace;
return fw ? fw.id : 1;
}
implicitWidth: Shared.Theme.barInnerWidth
implicitHeight: container.height
Rectangle {
id: container
width: parent.width
height: wsCol.height + padding * 2
radius: Shared.Theme.radiusNormal
color: Shared.Theme.surface0
property int padding: Shared.Theme.barPadding + 2
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
onWheel: event => {
if (event.angleDelta.y > 0)
Hyprland.dispatch("workspace m-1");
else
Hyprland.dispatch("workspace m+1");
}
}
// Sliding active indicator
Rectangle {
id: activeIndicator
property int targetIndex: {
if (root.monitorName) {
for (let i = 0; i < root.monitorWsData.length; i++) {
if (root.monitorWsData[i].id === root.activeWsId) return i;
}
return 0;
}
return Math.max(0, Math.min(root.activeWsId - 1, root.wsCount - 1));
}
property var targetItem: wsRepeater.itemAt(targetIndex)
x: (container.width - width) / 2
y: targetItem ? targetItem.y + wsCol.y : container.padding
width: root.cellSize
height: root.cellSize
radius: Shared.Theme.radiusSmall
color: Shared.Theme.red
Behavior on y {
NumberAnimation {
duration: Shared.Theme.animSlow
easing.type: Easing.InOutQuart
}
}
}
Column {
id: wsCol
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: container.padding
spacing: Math.floor(Shared.Theme.spacing / 2)
Repeater {
id: wsRepeater
model: root.monitorName ? root.monitorWsData.length : root.wsCount
delegate: Item {
id: wsItem
required property int index
width: root.cellSize
height: root.cellSize
property int wsId: root.monitorName ? root.monitorWsData[index].id : index + 1
property string wsName: root.monitorName ? root.monitorWsData[index].name : (index + 1).toString()
property bool isActive: root.activeWsId === wsId
property bool isOccupied: {
let all = Hyprland.workspaces;
for (let i = 0; i < all.values.length; i++) {
if (all.values[i].id === wsId)
return all.values[i].lastIpcObject?.windows > 0;
}
return false;
}
Text {
anchors.centerIn: parent
property string icon: root.iconMap[wsItem.wsName] ?? ""
property bool hasIcon: icon !== ""
text: hasIcon ? icon : wsItem.wsName
color: wsItem.isActive
? Shared.Theme.crust
: wsItem.isOccupied
? Shared.Theme.text
: Shared.Theme.overlay0
font.pixelSize: Shared.Theme.fontLarge
font.family: hasIcon ? Shared.Theme.iconFont : Shared.Theme.fontFamily
font.bold: !hasIcon && wsItem.isActive
Behavior on color {
ColorAnimation { duration: Shared.Theme.animFast }
}
}
MouseArea {
anchors.fill: parent
onClicked: Hyprland.dispatch("workspace name:" + wsItem.wsName)
}
}
}
}
}
}