- 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>
165 lines
5.6 KiB
QML
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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|