diff options
| author | Freya Murphy <freya@freyacat.org> | 2026-01-12 12:47:22 -0500 |
|---|---|---|
| committer | Freya Murphy <freya@freyacat.org> | 2026-01-12 12:47:22 -0500 |
| commit | 1a4cb7a87a6bccd0d9a2e769139cd86f5de53e8a (patch) | |
| tree | 9169031f3b18be961f07b99276d1b3e59d0b52f0 | |
| parent | remove default paths (diff) | |
| download | caelestia-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.qml | 11 | ||||
| -rw-r--r-- | config/Config.qml | 11 | ||||
| -rw-r--r-- | modules/bar/components/workspaces/ActiveIndicator.qml | 55 | ||||
| -rw-r--r-- | modules/bar/components/workspaces/SpecialWorkspaces.qml | 362 | ||||
| -rw-r--r-- | modules/bar/components/workspaces/Workspace.qml | 85 | ||||
| -rw-r--r-- | modules/bar/components/workspaces/Workspaces.qml | 90 |
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 - } } } |