(vertical) bar: refractor workspaces

This commit is contained in:
end-4
2025-09-06 08:44:39 +02:00
parent c86dbeb2b3
commit 6af3a1b5a7
3 changed files with 53 additions and 340 deletions
@@ -14,6 +14,7 @@ import Qt5Compat.GraphicalEffects
Item {
id: root
property bool vertical: false
property bool borderless: Config.options.bar.borderless
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.QsWindow.window?.screen)
readonly property Toplevel activeWindow: ToplevelManager.activeToplevel
@@ -22,6 +23,7 @@ Item {
property list<bool> workspaceOccupied: []
property int widgetPadding: 4
property int workspaceButtonWidth: 26
property real activeWorkspaceMargin: 2
property real workspaceIconSize: workspaceButtonWidth * 0.69
property real workspaceIconSizeShrinked: workspaceButtonWidth * 0.55
property real workspaceIconOpacityShrinked: 1
@@ -77,8 +79,8 @@ Item {
updateWorkspaceOccupied();
}
implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2
implicitHeight: Appearance.sizes.barHeight
implicitWidth: root.vertical ? Appearance.sizes.verticalBarWidth : backgroundLayout.implicitWidth
implicitHeight: root.vertical ? backgroundLayout.implicitHeight : Appearance.sizes.barHeight
// Scroll to switch workspaces
WheelHandler {
@@ -102,31 +104,36 @@ Item {
}
// Workspaces - background
RowLayout {
id: rowLayout
GridLayout {
id: backgroundLayout
z: 1
spacing: 0
anchors.fill: parent
implicitHeight: Appearance.sizes.barHeight
implicitHeight: root.vertical ? root.workspaceButtonWidth : Appearance.sizes.barHeight
implicitWidth: root.vertical ? Appearance.sizes.verticalBarWidth : root.workspaceButtonWidth
rowSpacing: 0
columnSpacing: 0
columns: root.vertical ? 1 : -1
Repeater {
model: Config.options.bar.workspaces.shown
Rectangle {
z: 1
Layout.alignment: root.vertical ? Qt.AlignHCenter : Qt.AlignVCenter
implicitWidth: workspaceButtonWidth
implicitHeight: workspaceButtonWidth
radius: (width / 2)
property var previousOccupied: (workspaceOccupied[index-1] && !(!activeWindow?.activated && monitor?.activeWorkspace?.id === index))
property var rightOccupied: (workspaceOccupied[index+1] && !(!activeWindow?.activated && monitor?.activeWorkspace?.id === index+2))
property var radiusLeft: previousOccupied ? 0 : (width / 2)
property var radiusRight: rightOccupied ? 0 : (width / 2)
property var radiusPrev: previousOccupied ? 0 : (width / 2)
property var radiusNext: rightOccupied ? 0 : (width / 2)
topLeftRadius: radiusLeft
bottomLeftRadius: radiusLeft
topRightRadius: radiusRight
bottomRightRadius: radiusRight
topLeftRadius: radiusPrev
bottomLeftRadius: root.vertical ? radiusNext : radiusPrev
topRightRadius: root.vertical ? radiusPrev : radiusNext
bottomRightRadius: radiusNext
color: ColorUtils.transparentize(Appearance.m3colors.m3secondaryContainer, 0.4)
opacity: (workspaceOccupied[index] && !(!activeWindow?.activated && monitor?.activeWorkspace?.id === index+1)) ? 1 : 0
@@ -134,11 +141,11 @@ Item {
Behavior on opacity {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
Behavior on radiusLeft {
Behavior on radiusPrev {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
Behavior on radiusRight {
Behavior on radiusNext {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
@@ -152,27 +159,34 @@ Item {
Rectangle {
z: 2
// Make active ws indicator, which has a brighter color, smaller to look like it is of the same size as ws occupied highlight
property real activeWorkspaceMargin: 2
implicitHeight: workspaceButtonWidth - activeWorkspaceMargin * 2
radius: Appearance.rounding.full
color: Appearance.colors.colPrimary
anchors.verticalCenter: parent.verticalCenter
anchors {
verticalCenter: vertical ? undefined : parent.verticalCenter
horizontalCenter: vertical ? parent.horizontalCenter : undefined
}
// idx1 is the "leading" indicator position, idx2 is the "following" one
// The former animates faster than the latter, see the NumberAnimations below
property real idx1: workspaceIndexInGroup
property real idx2: workspaceIndexInGroup
x: Math.min(idx1, idx2) * workspaceButtonWidth + activeWorkspaceMargin
implicitWidth: Math.abs(idx1 - idx2) * workspaceButtonWidth + workspaceButtonWidth - activeWorkspaceMargin * 2
property real indicatorPosition: Math.min(idx1, idx2) * workspaceButtonWidth + root.activeWorkspaceMargin
property real indicatorLength: Math.abs(idx1 - idx2) * workspaceButtonWidth + workspaceButtonWidth - root.activeWorkspaceMargin * 2
property real indicatorThickness: workspaceButtonWidth - root.activeWorkspaceMargin * 2
Behavior on activeWorkspaceMargin {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Behavior on idx1 { // Leading anim
x: root.vertical ? null : indicatorPosition
implicitWidth: root.vertical ? indicatorThickness : indicatorLength
y: root.vertical ? indicatorPosition : null
implicitHeight: root.vertical ? indicatorLength : indicatorThickness
Behavior on idx1 {
NumberAnimation {
duration: 100
easing.type: Easing.OutSine
}
}
Behavior on idx2 { // Following anim
Behavior on idx2 {
NumberAnimation {
duration: 300
easing.type: Easing.OutSine
@@ -181,13 +195,17 @@ Item {
}
// Workspaces - numbers
RowLayout {
GridLayout {
id: rowLayoutNumbers
z: 3
spacing: 0
columns: vertical ? 1 : -1
columnSpacing: 0
rowSpacing: 0
anchors.fill: parent
implicitHeight: Appearance.sizes.barHeight
implicitHeight: vertical ? Appearance.sizes.barWidth : Appearance.sizes.barHeight
implicitWidth: vertical ? Appearance.sizes.verticalBarWidth : Appearance.sizes.verticalBarWidth
Repeater {
model: Config.options.bar.workspaces.shown
@@ -195,10 +213,12 @@ Item {
Button {
id: button
property int workspaceValue: workspaceGroup * Config.options.bar.workspaces.shown + index + 1
Layout.fillHeight: true
Layout.fillHeight: !root.vertical
Layout.fillWidth: root.vertical
onPressed: Hyprland.dispatch(`workspace ${workspaceValue}`)
width: workspaceButtonWidth
width: vertical ? undefined : workspaceButtonWidth
height: vertical ? workspaceButtonWidth : undefined
background: Item {
id: workspaceButtonBackground
implicitWidth: workspaceButtonWidth
@@ -110,8 +110,9 @@ Item { // Bar content region
vertical: true
padding: 6
Workspaces {
Bar.Workspaces {
id: workspacesWidget
vertical: true
MouseArea {
// Right-click to toggle overview
anchors.fill: parent
@@ -1,308 +0,0 @@
import qs
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
import Quickshell.Hyprland
import Quickshell.Widgets
import Qt5Compat.GraphicalEffects
Item {
id: root
property bool borderless: Config.options.bar.borderless
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.QsWindow.window?.screen)
readonly property Toplevel activeWindow: ToplevelManager.activeToplevel
readonly property int workspaceGroup: Math.floor((monitor?.activeWorkspace?.id - 1) / Config.options.bar.workspaces.shown)
property list<bool> workspaceOccupied: []
property real workspaceButtonWidth: 26
property real activeWorkspaceMargin: 2
property real workspaceIconSize: workspaceButtonWidth * 0.69
property real workspaceIconSizeShrinked: workspaceButtonWidth * 0.55
property real workspaceIconOpacityShrinked: 1
property real workspaceIconMarginShrinked: -4
property int workspaceIndexInGroup: (monitor?.activeWorkspace?.id - 1) % Config.options.bar.workspaces.shown
implicitHeight: columnLayout.implicitHeight + columnLayout.spacing * 2
implicitWidth: Appearance.sizes.verticalBarWidth
property bool showNumbers: false
Timer {
id: showNumbersTimer
interval: (Config?.options.bar.autoHide.showWhenPressingSuper.delay ?? 100)
repeat: false
onTriggered: {
root.showNumbers = true
}
}
Connections {
target: GlobalStates
function onSuperDownChanged() {
if (!Config?.options.bar.autoHide.showWhenPressingSuper.enable) return;
if (GlobalStates.superDown) showNumbersTimer.restart();
else {
showNumbersTimer.stop();
root.showNumbers = false;
}
}
function onSuperReleaseMightTriggerChanged() {
showNumbersTimer.stop()
}
}
// Function to update workspaceOccupied
function updateWorkspaceOccupied() {
workspaceOccupied = Array.from({ length: Config.options.bar.workspaces.shown }, (_, i) => {
return Hyprland.workspaces.values.some(ws => ws.id === workspaceGroup * Config.options.bar.workspaces.shown + i + 1);
})
}
// Occupied workspace updates
Component.onCompleted: updateWorkspaceOccupied()
Connections {
target: Hyprland.workspaces
function onValuesChanged() {
updateWorkspaceOccupied();
}
}
Connections {
target: Hyprland
function onFocusedWorkspaceChanged() {
updateWorkspaceOccupied();
}
}
onWorkspaceGroupChanged: {
updateWorkspaceOccupied();
}
// Scroll to switch workspaces
WheelHandler {
onWheel: (event) => {
if (event.angleDelta.y < 0)
Hyprland.dispatch(`workspace r+1`);
else if (event.angleDelta.y > 0)
Hyprland.dispatch(`workspace r-1`);
}
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.BackButton
onPressed: (event) => {
if (event.button === Qt.BackButton) {
Hyprland.dispatch(`togglespecialworkspace`);
}
}
}
// Workspaces - background
ColumnLayout {
id: columnLayout
z: 1
spacing: 0
anchors.fill: parent
implicitWidth: Appearance.sizes.verticalBarWidth
Repeater {
model: Config.options.bar.workspaces.shown
Rectangle {
z: 1
implicitWidth: workspaceButtonWidth
implicitHeight: workspaceButtonWidth
radius: (width / 2)
property var previousOccupied: (workspaceOccupied[index-1] && !(!activeWindow?.activated && monitor?.activeWorkspace?.id === index))
property var nextOccupied: (workspaceOccupied[index+1] && !(!activeWindow?.activated && monitor?.activeWorkspace?.id === index+2))
property var radiusTop: previousOccupied ? 0 : (width / 2)
property var radiusBottom: nextOccupied ? 0 : (width / 2)
Layout.alignment: Qt.AlignHCenter
topLeftRadius: radiusTop
topRightRadius: radiusTop
bottomLeftRadius: radiusBottom
bottomRightRadius: radiusBottom
color: ColorUtils.transparentize(Appearance.m3colors.m3secondaryContainer, 0.4)
opacity: (workspaceOccupied[index] && !(!activeWindow?.activated && monitor?.activeWorkspace?.id === index+1)) ? 1 : 0
Behavior on opacity {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
Behavior on radiusTop {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
Behavior on radiusBottom {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
}
}
}
// Active workspace
Rectangle {
z: 2
// Make active ws indicator, which has a brighter color, smaller to look like it is of the same size as ws occupied highlight
implicitWidth: workspaceButtonWidth - root.activeWorkspaceMargin * 2
radius: Appearance.rounding.full
color: Appearance.colors.colPrimary
anchors.horizontalCenter: parent.horizontalCenter
property real idx1: workspaceIndexInGroup
property real idx2: workspaceIndexInGroup
y: Math.min(idx1, idx2) * workspaceButtonWidth + root.activeWorkspaceMargin
implicitHeight: Math.abs(idx1 - idx2) * workspaceButtonWidth + workspaceButtonWidth - root.activeWorkspaceMargin * 2
Behavior on idx1 { // Leading anim
NumberAnimation {
duration: 100
easing.type: Easing.OutSine
}
}
Behavior on idx2 { // Following anim
NumberAnimation {
duration: 300
easing.type: Easing.OutSine
}
}
}
// Workspaces - numbers
ColumnLayout {
id: columnLayoutNumbers
z: 3
spacing: 0
anchors.fill: parent
implicitWidth: Appearance.sizes.verticalBarWidth
Repeater {
model: Config.options.bar.workspaces.shown
Button {
id: button
property int workspaceValue: workspaceGroup * Config.options.bar.workspaces.shown + index + 1
Layout.fillWidth: true
onPressed: Hyprland.dispatch(`workspace ${workspaceValue}`)
height: workspaceButtonWidth
background: Item {
id: workspaceButtonBackground
implicitWidth: workspaceButtonWidth
implicitHeight: workspaceButtonWidth
property var biggestWindow: HyprlandData.biggestWindowForWorkspace(button.workspaceValue)
property var mainAppIconSource: Quickshell.iconPath(AppSearch.guessIcon(biggestWindow?.class), "image-missing")
StyledText { // Workspace number text
opacity: root.showNumbers
|| ((Config.options?.bar.workspaces.alwaysShowNumbers && (!Config.options?.bar.workspaces.showAppIcons || !workspaceButtonBackground.biggestWindow || root.showNumbers))
|| (root.showNumbers && !Config.options?.bar.workspaces.showAppIcons)
) ? 1 : 0
z: 3
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.pixelSize: Appearance.font.pixelSize.small - ((text.length - 1) * (text !== "10") * 2)
text: `${button.workspaceValue}`
elide: Text.ElideRight
color: (monitor?.activeWorkspace?.id == button.workspaceValue) ?
Appearance.m3colors.m3onPrimary :
(workspaceOccupied[index] ? Appearance.m3colors.m3onSecondaryContainer :
Appearance.colors.colOnLayer1Inactive)
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
}
Rectangle { // Dot instead of ws number
id: wsDot
opacity: (Config.options?.bar.workspaces.alwaysShowNumbers
|| root.showNumbers
|| (Config.options?.bar.workspaces.showAppIcons && workspaceButtonBackground.biggestWindow)
) ? 0 : 1
visible: opacity > 0
anchors.centerIn: parent
width: workspaceButtonWidth * 0.18
height: width
radius: width / 2
color: (monitor?.activeWorkspace?.id == button.workspaceValue) ?
Appearance.m3colors.m3onPrimary :
(workspaceOccupied[index] ? Appearance.m3colors.m3onSecondaryContainer :
Appearance.colors.colOnLayer1Inactive)
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
}
Item { // Main app icon
anchors.centerIn: parent
width: workspaceButtonWidth
height: workspaceButtonWidth
opacity: !Config.options?.bar.workspaces.showAppIcons ? 0 :
(workspaceButtonBackground.biggestWindow && !root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ?
1 : workspaceButtonBackground.biggestWindow ? workspaceIconOpacityShrinked : 0
visible: opacity > 0
IconImage {
id: mainAppIcon
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.bottomMargin: (!root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ?
(workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked
anchors.rightMargin: (!root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ?
(workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked
source: workspaceButtonBackground.mainAppIconSource
implicitSize: (!root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ? workspaceIconSize : workspaceIconSizeShrinked
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Behavior on anchors.bottomMargin {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Behavior on anchors.rightMargin {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Behavior on implicitSize {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
}
Loader {
active: Config.options.bar.workspaces.monochromeIcons
anchors.fill: mainAppIcon
sourceComponent: Item {
Desaturate {
id: desaturatedIcon
visible: false // There's already color overlay
anchors.fill: parent
source: mainAppIcon
desaturation: 0.8
}
ColorOverlay {
anchors.fill: desaturatedIcon
source: desaturatedIcon
color: ColorUtils.transparentize(wsDot.color, 0.9)
}
}
}
}
}
}
}
}
}