summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFreya Murphy <freya@freyacat.org>2026-01-12 12:47:22 -0500
committerFreya Murphy <freya@freyacat.org>2026-01-12 12:47:22 -0500
commit1a4cb7a87a6bccd0d9a2e769139cd86f5de53e8a (patch)
tree9169031f3b18be961f07b99276d1b3e59d0b52f0
parentremove default paths (diff)
downloadcaelestia-shell-1a4cb7a87a6bccd0d9a2e769139cd86f5de53e8a.tar.gz
caelestia-shell-1a4cb7a87a6bccd0d9a2e769139cd86f5de53e8a.tar.bz2
caelestia-shell-1a4cb7a87a6bccd0d9a2e769139cd86f5de53e8a.zip
refactor workspace functionality (real per monitor workspaces)
-rw-r--r--config/BarConfig.qml11
-rw-r--r--config/Config.qml11
-rw-r--r--modules/bar/components/workspaces/ActiveIndicator.qml55
-rw-r--r--modules/bar/components/workspaces/SpecialWorkspaces.qml362
-rw-r--r--modules/bar/components/workspaces/Workspace.qml85
-rw-r--r--modules/bar/components/workspaces/Workspaces.qml90
6 files changed, 36 insertions, 578 deletions
diff --git a/config/BarConfig.qml b/config/BarConfig.qml
index a0ce4be..00df1ea 100644
--- a/config/BarConfig.qml
+++ b/config/BarConfig.qml
@@ -66,18 +66,7 @@ JsonObject {
}
component Workspaces: JsonObject {
- property int shown: 5
- property bool activeIndicator: true
- property bool occupiedBg: false
- property bool showWindows: true
- property bool showWindowsOnSpecialWorkspaces: showWindows
- property bool activeTrail: false
property bool perMonitorWorkspaces: true
- property string label: " " // if empty, will show workspace name's first letter
- property string occupiedLabel: "󰮯"
- property string activeLabel: "󰮯"
- property string capitalisation: "preserve" // upper, lower, or preserve - relevant only if label is empty
- property list<var> specialWorkspaceIcons: []
}
component ActiveWindow: JsonObject {
diff --git a/config/Config.qml b/config/Config.qml
index c49240e..7a5c04f 100644
--- a/config/Config.qml
+++ b/config/Config.qml
@@ -166,18 +166,7 @@ Singleton {
statusIcons: bar.popouts.statusIcons
},
workspaces: {
- shown: bar.workspaces.shown,
- activeIndicator: bar.workspaces.activeIndicator,
- occupiedBg: bar.workspaces.occupiedBg,
- showWindows: bar.workspaces.showWindows,
- showWindowsOnSpecialWorkspaces: bar.workspaces.showWindowsOnSpecialWorkspaces,
- activeTrail: bar.workspaces.activeTrail,
perMonitorWorkspaces: bar.workspaces.perMonitorWorkspaces,
- label: bar.workspaces.label,
- occupiedLabel: bar.workspaces.occupiedLabel,
- activeLabel: bar.workspaces.activeLabel,
- capitalisation: bar.workspaces.capitalisation,
- specialWorkspaceIcons: bar.workspaces.specialWorkspaceIcons
},
tray: {
background: bar.tray.background,
diff --git a/modules/bar/components/workspaces/ActiveIndicator.qml b/modules/bar/components/workspaces/ActiveIndicator.qml
index 3e265e2..912f771 100644
--- a/modules/bar/components/workspaces/ActiveIndicator.qml
+++ b/modules/bar/components/workspaces/ActiveIndicator.qml
@@ -10,27 +10,20 @@ StyledRect {
required property int activeWsId
required property Repeater workspaces
required property Item mask
+ required property var monitorWorkspaces
readonly property int currentWsIdx: {
- let i = activeWsId - 1;
- while (i < 0)
- i += Config.bar.workspaces.shown;
- return i % Config.bar.workspaces.shown;
+ let wss = monitorWorkspaces;
+ for (let i = 0; i < wss.length; i++) {
+ let ws = wss[i];
+ if (ws.id == activeWsId)
+ return i;
+ }
}
- property real leading: workspaces.itemAt(currentWsIdx)?.y ?? 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 size: {
- const s = Math.abs(leading - trailing) + currentSize;
- if (Config.bar.workspaces.activeTrail && lastWs > currentWsIdx) {
- const ws = workspaces.itemAt(lastWs);
- // console.log(ws, lastWs);
- return ws ? Math.min(ws.y + ws.size - offset, s) : 0;
- }
- return s;
- }
+ property real offset: workspaces.itemAt(currentWsIdx)?.y ?? 0
+ property real size: Config.bar.sizes.innerWidth - Appearance.padding.small * 2
property int cWs
property int lastWs
@@ -42,7 +35,7 @@ StyledRect {
clip: true
y: offset + mask.y
- implicitWidth: Config.bar.sizes.innerWidth - Appearance.padding.small * 2
+ implicitWidth: size
implicitHeight: size
radius: Appearance.rounding.full
color: Colours.palette.m3primary
@@ -60,35 +53,7 @@ StyledRect {
anchors.horizontalCenter: parent.horizontalCenter
}
- Behavior on leading {
- enabled: Config.bar.workspaces.activeTrail
-
- EAnim {}
- }
-
- Behavior on trailing {
- enabled: Config.bar.workspaces.activeTrail
-
- EAnim {
- duration: Appearance.anim.durations.normal * 2
- }
- }
-
- Behavior on currentSize {
- enabled: Config.bar.workspaces.activeTrail
-
- EAnim {}
- }
-
Behavior on offset {
- enabled: !Config.bar.workspaces.activeTrail
-
- EAnim {}
- }
-
- Behavior on size {
- enabled: !Config.bar.workspaces.activeTrail
-
EAnim {}
}
diff --git a/modules/bar/components/workspaces/SpecialWorkspaces.qml b/modules/bar/components/workspaces/SpecialWorkspaces.qml
deleted file mode 100644
index ff2652e..0000000
--- a/modules/bar/components/workspaces/SpecialWorkspaces.qml
+++ /dev/null
@@ -1,362 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import qs.components
-import qs.components.effects
-import qs.services
-import qs.utils
-import qs.config
-import Quickshell
-import Quickshell.Hyprland
-import QtQuick
-import QtQuick.Layouts
-
-Item {
- id: root
-
- required property ShellScreen screen
- readonly property HyprlandMonitor monitor: Hypr.monitorFor(screen)
- readonly property string activeSpecial: (Config.bar.workspaces.perMonitorWorkspaces ? monitor : Hypr.focusedMonitor)?.lastIpcObject.specialWorkspace.name ?? ""
-
- layer.enabled: true
- layer.effect: OpacityMask {
- maskSource: mask
- }
-
- Item {
- id: mask
-
- anchors.fill: parent
- layer.enabled: true
- visible: false
-
- Rectangle {
- anchors.fill: parent
- radius: Appearance.rounding.full
-
- gradient: Gradient {
- orientation: Gradient.Vertical
-
- GradientStop {
- position: 0
- color: Qt.rgba(0, 0, 0, 0)
- }
- GradientStop {
- position: 0.3
- color: Qt.rgba(0, 0, 0, 1)
- }
- GradientStop {
- position: 0.7
- color: Qt.rgba(0, 0, 0, 1)
- }
- GradientStop {
- position: 1
- color: Qt.rgba(0, 0, 0, 0)
- }
- }
- }
-
- Rectangle {
- anchors.top: parent.top
- anchors.left: parent.left
- anchors.right: parent.right
-
- radius: Appearance.rounding.full
- implicitHeight: parent.height / 2
- opacity: view.contentY > 0 ? 0 : 1
-
- Behavior on opacity {
- Anim {}
- }
- }
-
- Rectangle {
- anchors.bottom: parent.bottom
- anchors.left: parent.left
- anchors.right: parent.right
-
- radius: Appearance.rounding.full
- implicitHeight: parent.height / 2
- opacity: view.contentY < view.contentHeight - parent.height + Appearance.padding.small ? 0 : 1
-
- Behavior on opacity {
- Anim {}
- }
- }
- }
-
- ListView {
- id: view
-
- anchors.fill: parent
- spacing: Appearance.spacing.normal
- interactive: false
-
- currentIndex: model.values.findIndex(w => w.name === root.activeSpecial)
- onCurrentIndexChanged: currentIndex = Qt.binding(() => model.values.findIndex(w => w.name === root.activeSpecial))
-
- model: ScriptModel {
- values: Hypr.workspaces.values.filter(w => w.name.startsWith("special:") && (!Config.bar.workspaces.perMonitorWorkspaces || w.monitor === root.monitor))
- }
-
- preferredHighlightBegin: 0
- preferredHighlightEnd: height
- highlightRangeMode: ListView.StrictlyEnforceRange
-
- highlightFollowsCurrentItem: false
- highlight: Item {
- y: view.currentItem?.y ?? 0
- implicitHeight: view.currentItem?.size ?? 0
-
- Behavior on y {
- Anim {}
- }
- }
-
- delegate: ColumnLayout {
- id: ws
-
- required property HyprlandWorkspace modelData
- readonly property int size: label.Layout.preferredHeight + (hasWindows ? windows.implicitHeight + Appearance.padding.small : 0)
- property int wsId
- property string icon
- property bool hasWindows
-
- anchors.left: view.contentItem.left
- anchors.right: view.contentItem.right
-
- spacing: 0
-
- Component.onCompleted: {
- wsId = modelData.id;
- icon = Icons.getSpecialWsIcon(modelData.name);
- hasWindows = Config.bar.workspaces.showWindowsOnSpecialWorkspaces && modelData.lastIpcObject.windows > 0;
- }
-
- // Hacky thing cause modelData gets destroyed before the remove anim finishes
- Connections {
- target: ws.modelData
-
- function onIdChanged(): void {
- if (ws.modelData)
- ws.wsId = ws.modelData.id;
- }
-
- function onNameChanged(): void {
- if (ws.modelData)
- ws.icon = Icons.getSpecialWsIcon(ws.modelData.name);
- }
-
- function onLastIpcObjectChanged(): void {
- if (ws.modelData)
- ws.hasWindows = Config.bar.workspaces.showWindowsOnSpecialWorkspaces && ws.modelData.lastIpcObject.windows > 0;
- }
- }
-
- Connections {
- target: Config.bar.workspaces
-
- function onShowWindowsOnSpecialWorkspacesChanged(): void {
- if (ws.modelData)
- ws.hasWindows = Config.bar.workspaces.showWindowsOnSpecialWorkspaces && ws.modelData.lastIpcObject.windows > 0;
- }
- }
-
- Loader {
- id: label
-
- Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
- Layout.preferredHeight: Config.bar.sizes.innerWidth - Appearance.padding.small * 2
-
- asynchronous: true
- sourceComponent: ws.icon.length === 1 ? letterComp : iconComp
-
- Component {
- id: iconComp
-
- MaterialIcon {
- fill: 1
- text: ws.icon
- verticalAlignment: Qt.AlignVCenter
- }
- }
-
- Component {
- id: letterComp
-
- StyledText {
- text: ws.icon
- verticalAlignment: Qt.AlignVCenter
- }
- }
- }
-
- Loader {
- id: windows
-
- Layout.alignment: Qt.AlignHCenter
- Layout.fillHeight: true
- Layout.preferredHeight: implicitHeight
-
- visible: active
- active: ws.hasWindows
- asynchronous: true
-
- sourceComponent: Column {
- spacing: 0
-
- add: Transition {
- Anim {
- properties: "scale"
- from: 0
- to: 1
- easing.bezierCurve: Appearance.anim.curves.standardDecel
- }
- }
-
- move: Transition {
- Anim {
- properties: "scale"
- to: 1
- easing.bezierCurve: Appearance.anim.curves.standardDecel
- }
- Anim {
- properties: "x,y"
- }
- }
-
- Repeater {
- model: ScriptModel {
- values: Hypr.toplevels.values.filter(c => c.workspace?.id === ws.wsId)
- }
-
- MaterialIcon {
- required property var modelData
-
- grade: 0
- text: Icons.getAppCategoryIcon(modelData.lastIpcObject.class, "terminal")
- color: Colours.palette.m3onSurfaceVariant
- }
- }
- }
-
- Behavior on Layout.preferredHeight {
- Anim {}
- }
- }
- }
-
- add: Transition {
- Anim {
- properties: "scale"
- from: 0
- to: 1
- easing.bezierCurve: Appearance.anim.curves.standardDecel
- }
- }
-
- remove: Transition {
- Anim {
- property: "scale"
- to: 0.5
- duration: Appearance.anim.durations.small
- }
- Anim {
- property: "opacity"
- to: 0
- duration: Appearance.anim.durations.small
- }
- }
-
- move: Transition {
- Anim {
- properties: "scale"
- to: 1
- easing.bezierCurve: Appearance.anim.curves.standardDecel
- }
- Anim {
- properties: "x,y"
- }
- }
-
- displaced: Transition {
- Anim {
- properties: "scale"
- to: 1
- easing.bezierCurve: Appearance.anim.curves.standardDecel
- }
- Anim {
- properties: "x,y"
- }
- }
- }
-
- Loader {
- active: Config.bar.workspaces.activeIndicator
- asynchronous: true
- anchors.fill: parent
-
- sourceComponent: Item {
- StyledClippingRect {
- id: indicator
-
- anchors.left: parent.left
- anchors.right: parent.right
-
- y: (view.currentItem?.y ?? 0) - view.contentY
- implicitHeight: view.currentItem?.size ?? 0
-
- color: Colours.palette.m3tertiary
- radius: Appearance.rounding.full
-
- Colouriser {
- source: view
- sourceColor: Colours.palette.m3onSurface
- colorizationColor: Colours.palette.m3onTertiary
-
- anchors.horizontalCenter: parent.horizontalCenter
-
- x: 0
- y: -indicator.y
- implicitWidth: view.width
- implicitHeight: view.height
- }
-
- Behavior on y {
- Anim {
- easing.bezierCurve: Appearance.anim.curves.emphasized
- }
- }
-
- Behavior on implicitHeight {
- Anim {
- easing.bezierCurve: Appearance.anim.curves.emphasized
- }
- }
- }
- }
- }
-
- MouseArea {
- property real startY
-
- anchors.fill: view
-
- drag.target: view.contentItem
- drag.axis: Drag.YAxis
- drag.maximumY: 0
- drag.minimumY: Math.min(0, view.height - view.contentHeight - Appearance.padding.small)
-
- onPressed: event => startY = event.y
-
- onClicked: event => {
- if (Math.abs(event.y - startY) > drag.threshold)
- return;
-
- const ws = view.itemAt(event.x, event.y);
- if (ws?.modelData)
- Hypr.dispatch(`togglespecialworkspace ${ws.modelData.name.slice(8)}`);
- else
- Hypr.dispatch("togglespecialworkspace special");
- }
- }
-}
diff --git a/modules/bar/components/workspaces/Workspace.qml b/modules/bar/components/workspaces/Workspace.qml
index bcfc190..1c1ca74 100644
--- a/modules/bar/components/workspaces/Workspace.qml
+++ b/modules/bar/components/workspaces/Workspace.qml
@@ -11,22 +11,20 @@ ColumnLayout {
required property int index
required property int activeWsId
- required property var occupied
- required property int groupOffset
+ required property var ws
- readonly property bool isWorkspace: true // Flag for finding workspace children
- // Unanimated prop for others to use as reference
- readonly property int size: implicitHeight + (hasWindows ? Appearance.padding.small : 0)
+ readonly property bool isOccupied: ws?.lastIpcObject?.windows > 0
+ readonly property bool isActive: ws?.id == activeWsId
+ readonly property bool isVisible: isActive || isOccupied || true
- readonly property int ws: groupOffset + index + 1
- readonly property bool isOccupied: occupied[ws] ?? false
- readonly property bool hasWindows: isOccupied && Config.bar.workspaces.showWindows
+ // Flag for finding workspace children
+ readonly property bool isWorkspace: true
Layout.alignment: Qt.AlignHCenter
- Layout.preferredHeight: size
+ Layout.preferredHeight: isVisible ? implicitHeight : 0
spacing: 0
- visible: Config.bar.workspaces.occupiedBg || root.isOccupied || root.activeWsId == root.ws
+ visible: isVisible
StyledText {
id: indicator
@@ -36,73 +34,16 @@ ColumnLayout {
animate: true
text: {
- const ws = Hypr.workspaces.values.find(w => w.id === root.ws);
- const wsName = !ws || ws.name == root.ws ? root.ws : ws.name[0];
+ let ws = root.ws;
+ let wsName = !ws || ws.name == ws.id ? ws.id : ws.name[0];
let displayName = wsName.toString();
- if (Config.bar.workspaces.capitalisation.toLowerCase() === "upper") {
- displayName = displayName.toUpperCase();
- } else if (Config.bar.workspaces.capitalisation.toLowerCase() === "lower") {
- displayName = displayName.toLowerCase();
- }
- const label = Config.bar.workspaces.label || displayName;
- 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;
+ let label = Config.bar.workspaces.label || displayName;
+ return label
}
- color: Config.bar.workspaces.occupiedBg || root.isOccupied || root.activeWsId === root.ws ? Colours.palette.m3onSurface : Colours.layer(Colours.palette.m3outlineVariant, 2)
+ color: root.isActive ? Colours.palette.m3onSurface : Colours.layer(Colours.palette.m3outlineVariant, 2)
verticalAlignment: Qt.AlignVCenter
}
- Loader {
- id: windows
-
- Layout.alignment: Qt.AlignHCenter
- Layout.fillHeight: true
- Layout.topMargin: -Config.bar.sizes.innerWidth / 10
-
- visible: active
- active: root.hasWindows
- asynchronous: true
-
- sourceComponent: Column {
- spacing: 0
-
- add: Transition {
- Anim {
- properties: "scale"
- from: 0
- to: 1
- easing.bezierCurve: Appearance.anim.curves.standardDecel
- }
- }
-
- move: Transition {
- Anim {
- properties: "scale"
- to: 1
- easing.bezierCurve: Appearance.anim.curves.standardDecel
- }
- Anim {
- properties: "x,y"
- }
- }
-
- Repeater {
- model: ScriptModel {
- values: Hypr.toplevels.values.filter(c => c.workspace?.id === root.ws)
- }
-
- MaterialIcon {
- required property var modelData
-
- grade: 0
- text: Icons.getAppCategoryIcon(modelData.lastIpcObject.class, "terminal")
- color: Colours.palette.m3onSurfaceVariant
- }
- }
- }
- }
-
Behavior on Layout.preferredHeight {
Anim {}
}
diff --git a/modules/bar/components/workspaces/Workspaces.qml b/modules/bar/components/workspaces/Workspaces.qml
index b146aae..69b8e48 100644
--- a/modules/bar/components/workspaces/Workspaces.qml
+++ b/modules/bar/components/workspaces/Workspaces.qml
@@ -13,16 +13,17 @@ StyledClippingRect {
required property ShellScreen screen
- readonly property bool onSpecial: (Config.bar.workspaces.perMonitorWorkspaces ? Hypr.monitorFor(screen) : Hypr.focusedMonitor)?.lastIpcObject.specialWorkspace.name !== ""
readonly property int activeWsId: Config.bar.workspaces.perMonitorWorkspaces ? (Hypr.monitorFor(screen).activeWorkspace?.id ?? 1) : Hypr.activeWsId
-
- readonly property var occupied: Hypr.workspaces.values.reduce((acc, curr) => {
- acc[curr.id] = curr.lastIpcObject.windows > 0;
- return acc;
- }, {})
- readonly property int groupOffset: Math.floor((activeWsId - 1) / Config.bar.workspaces.shown) * Config.bar.workspaces.shown
-
- property real blur: onSpecial ? 1 : 0
+ readonly property var monitorWorkspaces: {
+ if (Config.bar.workspaces.perMonitorWorkspaces) {
+ let monitor = Hypr.monitorFor(root.screen);
+ return Hypr.workspaces.values.filter(ws => {
+ return ws.monitor.id == monitor.id
+ });
+ } else {
+ return Hypr.workspaces.values
+ }
+ }
implicitWidth: Config.bar.sizes.innerWidth
implicitHeight: layout.implicitHeight + Appearance.padding.small * 2
@@ -32,29 +33,6 @@ StyledClippingRect {
Item {
anchors.fill: parent
- scale: root.onSpecial ? 0.8 : 1
- opacity: root.onSpecial ? 0.5 : 1
-
- layer.enabled: root.blur > 0
- layer.effect: MultiEffect {
- blurEnabled: true
- blur: root.blur
- blurMax: 32
- }
-
- Loader {
- active: Config.bar.workspaces.occupiedBg
- asynchronous: true
-
- anchors.fill: parent
- anchors.margins: Appearance.padding.small
-
- sourceComponent: OccupiedBg {
- workspaces: workspaces
- occupied: root.occupied
- groupOffset: root.groupOffset
- }
- }
ColumnLayout {
id: layout
@@ -64,24 +42,21 @@ StyledClippingRect {
Repeater {
id: workspaces
-
- model: Config.bar.workspaces.shown
-
+ model: monitorWorkspaces.length
Workspace {
+ ws: monitorWorkspaces[index]
activeWsId: root.activeWsId
- occupied: root.occupied
- groupOffset: root.groupOffset
}
}
}
Loader {
anchors.horizontalCenter: parent.horizontalCenter
- active: Config.bar.workspaces.activeIndicator
asynchronous: true
sourceComponent: ActiveIndicator {
activeWsId: root.activeWsId
+ monitorWorkspaces: root.monitorWorkspaces
workspaces: workspaces
mask: layout
}
@@ -97,44 +72,5 @@ StyledClippingRect {
Hypr.dispatch("togglespecialworkspace special");
}
}
-
- Behavior on scale {
- Anim {}
- }
-
- Behavior on opacity {
- Anim {}
- }
- }
-
- Loader {
- id: specialWs
-
- anchors.fill: parent
- anchors.margins: Appearance.padding.small
-
- active: opacity > 0
- asynchronous: true
-
- scale: root.onSpecial ? 1 : 0.5
- opacity: root.onSpecial ? 1 : 0
-
- sourceComponent: SpecialWorkspaces {
- screen: root.screen
- }
-
- Behavior on scale {
- Anim {}
- }
-
- Behavior on opacity {
- Anim {}
- }
- }
-
- Behavior on blur {
- Anim {
- duration: Appearance.anim.durations.small
- }
}
}