bar/workspaces: refactor

This commit is contained in:
2 * r + 2 * t 2025-08-16 00:10:48 +10:00
parent 2be46ff65a
commit d8f7bac960
9 changed files with 98 additions and 111 deletions

View file

@ -254,13 +254,12 @@ All configuration options are in `~/.config/caelestia/shell.json`.
}, },
"workspaces": { "workspaces": {
"activeIndicator": true, "activeIndicator": true,
"activeLabel": "󰮯 ", "activeLabel": "󰮯",
"activeTrail": false, "activeTrail": false,
"label": " ", "label": " ",
"occupiedBg": false, "occupiedBg": false,
"occupiedLabel": "󰮯 ", "occupiedLabel": "󰮯",
"perMonitorWorkspaces": true, "perMonitorWorkspaces": true,
"rounded": true,
"showWindows": true, "showWindows": true,
"shown": 5 "shown": 5
} }

View file

@ -50,15 +50,14 @@ JsonObject {
component Workspaces: JsonObject { component Workspaces: JsonObject {
property int shown: 5 property int shown: 5
property bool rounded: true
property bool activeIndicator: true property bool activeIndicator: true
property bool occupiedBg: false property bool occupiedBg: false
property bool showWindows: true property bool showWindows: true
property bool activeTrail: false property bool activeTrail: false
property bool perMonitorWorkspaces: true property bool perMonitorWorkspaces: true
property string label: " " property string label: " "
property string occupiedLabel: "󰮯 " property string occupiedLabel: "󰮯"
property string activeLabel: "󰮯 " property string activeLabel: "󰮯"
} }
component Tray: JsonObject { component Tray: JsonObject {

View file

@ -177,6 +177,5 @@ ColumnLayout {
visible: enabled visible: enabled
active: enabled active: enabled
asynchronous: true
} }
} }

View file

@ -73,7 +73,6 @@ Item {
anchors.right: parent.right anchors.right: parent.right
active: root.shouldBeVisible || root.visible active: root.shouldBeVisible || root.visible
asynchronous: true
sourceComponent: Bar { sourceComponent: Bar {
width: root.contentWidth width: root.contentWidth

View file

@ -8,45 +8,38 @@ StyledRect {
id: root id: root
required property int activeWsId required property int activeWsId
required property list<Workspace> workspaces required property Repeater workspaces
required property Item mask required property Item mask
required property real maskWidth
required property real maskHeight
required property int groupOffset
readonly property int currentWsIdx: activeWsId - 1 - groupOffset readonly property int currentWsIdx: (activeWsId - 1) % Config.bar.workspaces.shown
property real leading: getWsY(currentWsIdx)
property real trailing: getWsY(currentWsIdx) property real leading: workspaces.itemAt(currentWsIdx)?.y ?? 0
property real currentSize: workspaces[currentWsIdx]?.size ?? 0 property real trailing: workspaces.itemAt(currentWsIdx)?.y ?? 0
property real currentSize: workspaces.itemAt(currentWsIdx)?.size ?? 0
property real offset: Math.min(leading, trailing) property real offset: Math.min(leading, trailing)
property real size: { property real size: {
const s = Math.abs(leading - trailing) + currentSize; const s = Math.abs(leading - trailing) + currentSize;
if (Config.bar.workspaces.activeTrail && lastWs > currentWsIdx) if (Config.bar.workspaces.activeTrail && lastWs > currentWsIdx) {
return Math.min(getWsY(lastWs) + (workspaces[lastWs]?.size ?? 0) - offset, s); const ws = workspaces.itemAt(lastWs);
// console.log(ws, lastWs);
return ws ? Math.min(ws.y + ws.size - offset, s) : 0;
}
return s; return s;
} }
property int cWs property int cWs
property int lastWs property int lastWs
function getWsY(idx: int): real {
let y = 0;
for (let i = 0; i < idx; i++)
y += workspaces[i]?.size ?? 0;
return y;
}
onCurrentWsIdxChanged: { onCurrentWsIdxChanged: {
lastWs = cWs; lastWs = cWs;
cWs = currentWsIdx; cWs = currentWsIdx;
} }
clip: true clip: true
x: 1 y: offset + mask.y
y: offset + 1 implicitWidth: Config.bar.sizes.innerWidth - Appearance.padding.small * 2
implicitWidth: Config.bar.sizes.innerHeight - 2 implicitHeight: size
implicitHeight: size - 2 radius: Appearance.rounding.full
radius: Config.bar.workspaces.rounded ? Appearance.rounding.full : 0
color: Colours.palette.m3primary color: Colours.palette.m3primary
Colouriser { Colouriser {
@ -56,8 +49,8 @@ StyledRect {
x: 0 x: 0
y: -parent.offset y: -parent.offset
implicitWidth: root.maskWidth implicitWidth: root.mask.implicitWidth
implicitHeight: root.maskHeight implicitHeight: root.mask.implicitHeight
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }

View file

@ -9,7 +9,7 @@ import QtQuick
Item { Item {
id: root id: root
required property list<Workspace> workspaces required property Repeater workspaces
required property var occupied required property var occupied
required property int groupOffset required property int groupOffset
@ -48,18 +48,17 @@ Item {
required property var modelData required property var modelData
readonly property Workspace start: root.workspaces[modelData.start - 1 - root.groupOffset] ?? null readonly property Workspace start: root.workspaces.itemAt((modelData.start - 1) % Config.bar.workspaces.shown) ?? null
readonly property Workspace end: root.workspaces[modelData.end - 1 - root.groupOffset] ?? null readonly property Workspace end: root.workspaces.itemAt((modelData.end - 1) % Config.bar.workspaces.shown) ?? null
color: Colours.tPalette.m3surfaceContainerHigh anchors.horizontalCenter: root.horizontalCenter
radius: Config.bar.workspaces.rounded ? Appearance.rounding.full : 0
x: start?.x ?? 0 y: (start?.y ?? 0) - 1
y: start?.y ?? 0 implicitWidth: Config.bar.sizes.innerWidth - Appearance.padding.small * 2 + 2
implicitWidth: Config.bar.sizes.innerHeight implicitHeight: start && end ? end.y + end.size - start.y + 2 : 0
implicitHeight: end?.y + end?.height - start?.y
anchors.horizontalCenter: parent.horizontalCenter color: Colours.layer(Colours.palette.m3surfaceContainerHigh, 2)
radius: Appearance.rounding.full
scale: 0 scale: 0
Component.onCompleted: scale = 1 Component.onCompleted: scale = 1
@ -70,11 +69,11 @@ Item {
} }
} }
Behavior on x { Behavior on y {
Anim {} Anim {}
} }
Behavior on y { Behavior on implicitHeight {
Anim {} Anim {}
} }
} }

View file

@ -6,7 +6,7 @@ import Quickshell
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
Item { ColumnLayout {
id: root id: root
required property int index required property int index
@ -16,41 +16,44 @@ Item {
readonly property bool isWorkspace: true // Flag for finding workspace children readonly property bool isWorkspace: true // Flag for finding workspace children
// Unanimated prop for others to use as reference // Unanimated prop for others to use as reference
readonly property real size: childrenRect.height + (hasWindows ? Appearance.padding.smaller : 0) readonly property int size: implicitHeight + (hasWindows ? Appearance.padding.small : 0)
readonly property int ws: groupOffset + index + 1 readonly property int ws: groupOffset + index + 1
readonly property bool isOccupied: occupied[ws] ?? false readonly property bool isOccupied: occupied[ws] ?? false
readonly property bool hasWindows: isOccupied && Config.bar.workspaces.showWindows readonly property bool hasWindows: isOccupied && Config.bar.workspaces.showWindows
Layout.preferredWidth: childrenRect.width Layout.alignment: Qt.AlignHCenter
Layout.preferredHeight: size Layout.preferredHeight: size
spacing: 0
StyledText { StyledText {
id: indicator id: indicator
readonly property string label: Config.bar.workspaces.label || root.ws Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
readonly property string occupiedLabel: Config.bar.workspaces.occupiedLabel || label Layout.preferredHeight: Config.bar.sizes.innerWidth - Appearance.padding.small * 2
readonly property string activeLabel: Config.bar.workspaces.activeLabel || (root.isOccupied ? occupiedLabel : label)
animate: true animate: true
text: root.activeWsId === root.ws ? activeLabel : root.isOccupied ? occupiedLabel : label text: {
const label = Config.bar.workspaces.label || root.ws;
const occupiedLabel = Config.bar.workspaces.occupiedLabel || label;
const activeLabel = Config.bar.workspaces.activeLabel || (root.isOccupied ? occupiedLabel : label);
return root.activeWsId === root.ws ? activeLabel : root.isOccupied ? occupiedLabel : label;
}
color: Config.bar.workspaces.occupiedBg || root.isOccupied || root.activeWsId === root.ws ? Colours.palette.m3onSurface : Colours.layer(Colours.palette.m3outlineVariant, 2) color: Config.bar.workspaces.occupiedBg || root.isOccupied || root.activeWsId === root.ws ? Colours.palette.m3onSurface : Colours.layer(Colours.palette.m3outlineVariant, 2)
horizontalAlignment: StyledText.AlignHCenter verticalAlignment: Qt.AlignVCenter
verticalAlignment: StyledText.AlignVCenter
width: Config.bar.sizes.innerHeight
height: Config.bar.sizes.innerHeight
} }
Loader { Loader {
id: windows id: windows
active: Config.bar.workspaces.showWindows Layout.alignment: Qt.AlignHCenter
asynchronous: true Layout.fillHeight: true
Layout.topMargin: -Config.bar.sizes.innerWidth / 10
anchors.horizontalCenter: indicator.horizontalCenter visible: active
anchors.top: indicator.bottom active: root.hasWindows
anchors.topMargin: -Config.bar.sizes.innerHeight / 10 asynchronous: true
sourceComponent: Column { sourceComponent: Column {
spacing: 0 spacing: 0
@ -91,10 +94,6 @@ Item {
} }
} }
Behavior on Layout.preferredWidth {
Anim {}
}
Behavior on Layout.preferredHeight { Behavior on Layout.preferredHeight {
Anim {} Anim {}
} }

View file

@ -13,76 +13,61 @@ StyledRect {
required property ShellScreen screen required property ShellScreen screen
readonly property int activeWsId: Config.bar.workspaces.perMonitorWorkspaces ? (Hyprland.monitorFor(screen).activeWorkspace?.id ?? 1) : Hyprland.activeWsId readonly property int activeWsId: Config.bar.workspaces.perMonitorWorkspaces ? (Hyprland.monitorFor(screen).activeWorkspace?.id ?? 1) : Hyprland.activeWsId
readonly property list<Workspace> workspaces: layout.children.filter(c => c.isWorkspace).sort((w1, w2) => w1.ws - w2.ws)
readonly property var occupied: Hyprland.workspaces.values.reduce((acc, curr) => { readonly property var occupied: Hyprland.workspaces.values.reduce((acc, curr) => {
acc[curr.id] = curr.lastIpcObject.windows > 0; acc[curr.id] = curr.lastIpcObject.windows > 0;
return acc; return acc;
}, {}) }, {})
readonly property int groupOffset: Math.floor((activeWsId - 1) / Config.bar.workspaces.shown) * Config.bar.workspaces.shown readonly property int groupOffset: Math.floor((activeWsId - 1) / Config.bar.workspaces.shown) * Config.bar.workspaces.shown
implicitWidth: layout.implicitWidth + Appearance.padding.small * 2
implicitHeight: layout.implicitHeight + Appearance.padding.small * 2 implicitHeight: layout.implicitHeight + Appearance.padding.small * 2
implicitWidth: Config.bar.sizes.innerWidth
color: Colours.tPalette.m3surfaceContainer color: Colours.tPalette.m3surfaceContainer
radius: Appearance.rounding.full radius: Appearance.rounding.full
Item { Loader {
id: inner active: Config.bar.workspaces.occupiedBg
asynchronous: true
anchors.fill: parent anchors.fill: parent
anchors.margins: Appearance.padding.small anchors.margins: Appearance.padding.small
ColumnLayout { sourceComponent: OccupiedBg {
id: layout workspaces: workspaces
occupied: root.occupied
spacing: 0 groupOffset: root.groupOffset
Repeater {
model: Config.bar.workspaces.shown
Workspace {
activeWsId: root.activeWsId
occupied: root.occupied
groupOffset: root.groupOffset
}
}
} }
}
Loader { ColumnLayout {
active: Config.bar.workspaces.occupiedBg id: layout
asynchronous: true
z: -1 anchors.centerIn: parent
anchors.fill: parent spacing: Math.floor(Appearance.spacing.small / 2)
sourceComponent: OccupiedBg { Repeater {
workspaces: root.workspaces id: workspaces
model: Config.bar.workspaces.shown
Workspace {
activeWsId: root.activeWsId
occupied: root.occupied occupied: root.occupied
groupOffset: root.groupOffset groupOffset: root.groupOffset
} }
} }
}
Loader { Loader {
active: Config.bar.workspaces.activeIndicator anchors.horizontalCenter: parent.horizontalCenter
asynchronous: true active: Config.bar.workspaces.activeIndicator
asynchronous: true
sourceComponent: ActiveIndicator { sourceComponent: ActiveIndicator {
activeWsId: root.activeWsId activeWsId: root.activeWsId
workspaces: root.workspaces workspaces: workspaces
mask: layout mask: layout
maskWidth: inner.width
maskHeight: inner.height
groupOffset: root.groupOffset
}
}
MouseArea {
anchors.fill: parent
onPressed: event => {
const ws = layout.childAt(event.x, event.y).index + root.groupOffset + 1;
if (Hyprland.activeWsId !== ws)
Hyprland.dispatch(`workspace ${ws}`);
}
} }
} }
} }

View file

@ -183,4 +183,19 @@ Singleton {
return "volume_down"; return "volume_down";
return "volume_mute"; return "volume_mute";
} }
function getSpecialWsIcon(name: string): string {
name = name.toLowerCase().slice("special:".length);
if (name === "special")
return "star";
if (name === "communication")
return "forum";
if (name === "music")
return "music_note";
if (name === "todo")
return "checklist";
if (name === "sysmon")
return "monitor_heart";
return name[0].toUpperCase();
}
} }