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>
This commit is contained in:
164
dot_config/quickshell/bar/Workspaces.qml
Normal file
164
dot_config/quickshell/bar/Workspaces.qml
Normal file
@@ -0,0 +1,164 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user