From 92636a2ddefbc62123c53c356b9bbb87e3af9f06 Mon Sep 17 00:00:00 2001 From: doloro Date: Wed, 30 Jul 2025 23:21:13 +0100 Subject: [PATCH] workspace view rework --- quickshell/bar/Bar.qml | 12 +-- quickshell/bar/widgets/Audio.qml | 33 +++++-- quickshell/bar/widgets/Workspaces.qml | 3 + quickshell/bar/widgets/clock/Clock.qml | 3 +- quickshell/bar/widgets/common/Colors.qml | 12 +++ quickshell/bar/widgets/common/Icons.qml | 27 ++++++ .../bar/widgets/common/MaterialIcon.qml | 17 ++++ .../bar/widgets/common/text/StyledText.qml | 54 +++++++++++ .../bar/widgets/player/PlayerControllerV2.qml | 11 +++ .../bar/widgets/player/PlayerPopupV2.qml | 2 +- .../bar/widgets/workspace/HyprlandClient.qml | 7 ++ .../workspace/HyprlandWindowTracker.qml | 29 ++++++ .../bar/widgets/workspace/WorkspaceWidget.qml | 90 +++++++++++++++++++ 13 files changed, 285 insertions(+), 15 deletions(-) create mode 100644 quickshell/bar/widgets/common/Colors.qml create mode 100644 quickshell/bar/widgets/common/Icons.qml create mode 100644 quickshell/bar/widgets/common/MaterialIcon.qml create mode 100644 quickshell/bar/widgets/common/text/StyledText.qml create mode 100644 quickshell/bar/widgets/workspace/HyprlandClient.qml create mode 100644 quickshell/bar/widgets/workspace/HyprlandWindowTracker.qml create mode 100644 quickshell/bar/widgets/workspace/WorkspaceWidget.qml diff --git a/quickshell/bar/Bar.qml b/quickshell/bar/Bar.qml index 9243c39..5e84935 100644 --- a/quickshell/bar/Bar.qml +++ b/quickshell/bar/Bar.qml @@ -4,11 +4,13 @@ import Quickshell.Io import QtQuick import QtQuick.Layouts import Quickshell.Widgets +import Quickshell.Hyprland import "widgets" as Widgets import "widgets/player" as Player import "widgets/common" as Common import "widgets/clock" as Clock +import "widgets/workspace" as Workspace // Tako kindly threatened you to sort the naming schema and to put all the svg's in an asset folder, so please do that @@ -37,26 +39,26 @@ import "widgets/clock" as Clock RowLayout { // Left Layout.alignment: Qt.AlignLeft Clock.Date {} + Clock.Clock {} + Workspace.WorkspaceWidget {} } RowLayout { // Center // TODO: add icons of the active window per workspace in the workspace tab anchors.centerIn: parent - Widgets.Workspaces {} } RowLayout { // Right Layout.alignment: Qt.AlignRight - Common.VerticalSeprator {} Loader { sourceComponent: Widgets.Audio {} } RowLayout { + Text { + text: HyprlandWindowTracker.HyprlandWindowTracker.aaaa + } visible: Player.activePlayer.isPlaying() - Common.VerticalSeprator {} Player.PlayerWidgetV2 { } } - Common.VerticalSeprator {} - Clock.Clock {} Widgets.SystemTray { id: systemTray } diff --git a/quickshell/bar/widgets/Audio.qml b/quickshell/bar/widgets/Audio.qml index aed1b02..d09cb4f 100644 --- a/quickshell/bar/widgets/Audio.qml +++ b/quickshell/bar/widgets/Audio.qml @@ -4,6 +4,8 @@ import QtQuick import QtQuick.Layouts import Quickshell.Widgets import Quickshell.Services.Pipewire +import "common" as Common + Item { id: root @@ -27,17 +29,32 @@ Item { radius: 7 color: "black" } - IconImage { + Text { anchors.centerIn: parent - implicitSize: 25 - source: root.speakerNode.audio.muted ? "root:/assets/bar/MutedSpeaker.svg" : "root:/assets/bar/Speaker.svg" - // source: "root:/assets/bar/Speaker.svg" + // implicitSize: 25 + font.pixelSize: 28 + color: root.speakerNode.audio.muted ? "#FF474C" : "white" + font.family: "JetBrainsMono Nerd Font Mono" + text: root.speakerNode.audio.muted ? Common.Icons.audioIcons.speakerMuted : Common.Icons.audioIcons.speaker } } - IconImage { - implicitSize: 25 - source: root.microphoneNode.audio.muted ? "root:/assets/bar/MutedMic.svg" : "root:/assets/bar/Mic.svg" - // source: "root:/assets/bar/Mic.svg" + Item { + implicitWidth: 28 + implicitHeight: 28 + Rectangle { + width: parent.width + height: parent.height + radius: 7 + color: "black" + } + Text { + anchors.centerIn: parent + // implicitSize: 25 + font.pixelSize: 34 + color: root.microphoneNode.audio.muted ? "#FF474C" : "white" + font.family: "JetBrainsMono Nerd Font Mono" + text: root.microphoneNode.audio.muted ? Common.Icons.audioIcons.microphoneMuted : Common.Icons.audioIcons.microphone + } } } } diff --git a/quickshell/bar/widgets/Workspaces.qml b/quickshell/bar/widgets/Workspaces.qml index 2c0c97b..61df8a6 100644 --- a/quickshell/bar/widgets/Workspaces.qml +++ b/quickshell/bar/widgets/Workspaces.qml @@ -35,6 +35,9 @@ RowLayout { if (event.button === Qt.LeftButton) { Hyprland.dispatch('workspace ' + index_workspace.id); } + for (x in index_workspace.toplevels.values) { + console.log(index_workspace.toplevels.values[x].title) + } } } } diff --git a/quickshell/bar/widgets/clock/Clock.qml b/quickshell/bar/widgets/clock/Clock.qml index ec96137..2d95795 100644 --- a/quickshell/bar/widgets/clock/Clock.qml +++ b/quickshell/bar/widgets/clock/Clock.qml @@ -6,10 +6,11 @@ import "../common" as Common Item { id: root - width: text.width + width: text.width + 15 height: text.height Common.Container {} Text { + anchors.centerIn: parent id: text text: ClockData.time font.pointSize: 10.25 diff --git a/quickshell/bar/widgets/common/Colors.qml b/quickshell/bar/widgets/common/Colors.qml new file mode 100644 index 0000000..310f069 --- /dev/null +++ b/quickshell/bar/widgets/common/Colors.qml @@ -0,0 +1,12 @@ +pragma Singleton + +import Quickshell +import Quickshell.Io + +Singleton { + id: root + + readonly property var colors: ({ + "primary": "", + }) +} diff --git a/quickshell/bar/widgets/common/Icons.qml b/quickshell/bar/widgets/common/Icons.qml new file mode 100644 index 0000000..192f20a --- /dev/null +++ b/quickshell/bar/widgets/common/Icons.qml @@ -0,0 +1,27 @@ +pragma Singleton + +import Quickshell +import Quickshell.Io + +Singleton { + id: root + + readonly property var audioIcons: ({ + microphone: "", + microphoneMuted: "", + speaker: "󰕾", + speakerMuted: "󰖁", + }) + + // {class}: {icon} + readonly property var appIcons: ({ + "vesktop": "", + "steam": "", + "org.telegram.desktop": "", + "chromium": "", + "dev.zed.Zed": "", + "foot": "", + "gamescope": "󰺵", + "fallback": "" + }) +} diff --git a/quickshell/bar/widgets/common/MaterialIcon.qml b/quickshell/bar/widgets/common/MaterialIcon.qml new file mode 100644 index 0000000..34b2479 --- /dev/null +++ b/quickshell/bar/widgets/common/MaterialIcon.qml @@ -0,0 +1,17 @@ +// import qs.services +// import qs.config +import "text" as Text + +Text.StyledText { + property real fill + property int grade: Colours.light ? 0 : -25 + + font.family: Appearance.font.family.material + font.pointSize: Appearance.font.size.larger + font.variableAxes: ({ + FILL: fill.toFixed(1), + GRAD: grade, + opsz: fontInfo.pixelSize, + wght: fontInfo.weight + }) +} diff --git a/quickshell/bar/widgets/common/text/StyledText.qml b/quickshell/bar/widgets/common/text/StyledText.qml new file mode 100644 index 0000000..e03157a --- /dev/null +++ b/quickshell/bar/widgets/common/text/StyledText.qml @@ -0,0 +1,54 @@ +pragma ComponentBehavior: Bound + +// https://github.com/caelestia-dots/shell/blob/a23bb48c243348096b27f5f4ac94f663271ece10/widgets/StyledText.qml + +// import qs.services +// import qs.config +import QtQuick + +Text { + id: root + + property bool animate: false + property string animateProp: "scale" + property real animateFrom: 0 + property real animateTo: 1 + property int animateDuration: Appearance.anim.durations.normal + + renderType: Text.NativeRendering + textFormat: Text.PlainText + color: Colours.palette.m3onSurface + font.family: Appearance.font.family.sans + font.pointSize: Appearance.font.size.smaller + + Behavior on color { + ColorAnimation { + duration: Appearance.anim.durations.normal + easing.type: Easing.BezierSpline + easing.bezierCurve: Appearance.anim.curves.standard + } + } + + Behavior on text { + enabled: root.animate + + SequentialAnimation { + Anim { + to: root.animateFrom + easing.bezierCurve: Appearance.anim.curves.standardAccel + } + PropertyAction {} + Anim { + to: root.animateTo + easing.bezierCurve: Appearance.anim.curves.standardDecel + } + } + } + + component Anim: NumberAnimation { + target: root + property: root.animateProp + duration: root.animateDuration / 2 + easing.type: Easing.BezierSpline + } +} diff --git a/quickshell/bar/widgets/player/PlayerControllerV2.qml b/quickshell/bar/widgets/player/PlayerControllerV2.qml index 49d6287..44c1d26 100644 --- a/quickshell/bar/widgets/player/PlayerControllerV2.qml +++ b/quickshell/bar/widgets/player/PlayerControllerV2.qml @@ -31,9 +31,11 @@ Singleton { property string lastActivePlayerIdentify: ""; onReloaded: { + root.setActivePlayerFromIdentity(lastActivePlayerIdentify) root.updateActiveTrackPosition() } onLoaded: { + root.setActivePlayerFromIdentity(lastActivePlayerIdentify) root.updateActiveTrackPosition() } } @@ -166,6 +168,15 @@ Singleton { this.activePlayer = player; } + function setActivePlayerFromIdentity(identify: string) { + for (x in root.mpris.players) { + if (x.identify == identify) { + this.activePlayer = x + return + } + } + } + Timer { // only emit the signal when the position is actually changing. running: root.activePlayer.playbackState == MprisPlaybackState.Playing diff --git a/quickshell/bar/widgets/player/PlayerPopupV2.qml b/quickshell/bar/widgets/player/PlayerPopupV2.qml index 6e75461..2838a25 100644 --- a/quickshell/bar/widgets/player/PlayerPopupV2.qml +++ b/quickshell/bar/widgets/player/PlayerPopupV2.qml @@ -220,7 +220,6 @@ PopupWindow { repeat: true onTriggered: { root.modelData.position = slider.value - audioPopup.player.update(); // force values to update // causing ui to update with it slider.debounceValue = false; } } @@ -245,6 +244,7 @@ PopupWindow { updateTimer.tempOneshot = false; slider.lastPosition = slider.value slider.value = root.modelData.position + // root.audioPopup.player.update(); } } } diff --git a/quickshell/bar/widgets/workspace/HyprlandClient.qml b/quickshell/bar/widgets/workspace/HyprlandClient.qml new file mode 100644 index 0000000..89d18d8 --- /dev/null +++ b/quickshell/bar/widgets/workspace/HyprlandClient.qml @@ -0,0 +1,7 @@ +import Quickshell +import QtQuick + +Item { + property string initialClass: ""; + property string initialTitle: ""; +} diff --git a/quickshell/bar/widgets/workspace/HyprlandWindowTracker.qml b/quickshell/bar/widgets/workspace/HyprlandWindowTracker.qml new file mode 100644 index 0000000..75dd302 --- /dev/null +++ b/quickshell/bar/widgets/workspace/HyprlandWindowTracker.qml @@ -0,0 +1,29 @@ +pragma Singleton + +import Quickshell +import QtQuick +import Quickshell.Io +import Quickshell.Hyprland + +Singleton { + id: root + // property string aaaa: ""; + property list meow; + property Hyprland hyprland: Hyprland; + + Process { + id: dateProc + + command: ["hyprctl", "clients"] + running: true + + stdout: StdioCollector { + onStreamFinished: parseClients(this.text) + } + } + function parseClients(text) { + hyprland.refreshToplevels(); + var meow = hyprland.activeToplevel.lastIpcObject; + console.log(meow.class); + } +} diff --git a/quickshell/bar/widgets/workspace/WorkspaceWidget.qml b/quickshell/bar/widgets/workspace/WorkspaceWidget.qml new file mode 100644 index 0000000..05b1a58 --- /dev/null +++ b/quickshell/bar/widgets/workspace/WorkspaceWidget.qml @@ -0,0 +1,90 @@ +import Quickshell +import Quickshell.Io +import QtQuick +import QtQuick.Layouts +import Quickshell.Widgets +import Quickshell.Hyprland +import "root:/bar/widgets/common" as Common + +RowLayout { + id: root + spacing: 10 + Layout.fillHeight: true + + Repeater { + model: Hyprland.workspaces + delegate: Item { + id: root2 + required property HyprlandWorkspace modelData + implicitWidth: 35 + appIconContainer.implicitWidth + Layout.fillHeight: true + Item { + visible: root2.modelData.toplevels.values[0] != undefined + id: appIconContainer + implicitWidth: row.implicitWidth + implicitHeight: 25 + x: 30 + anchors.verticalCenter: root2.verticalCenter + Rectangle { + x: -10 + radius: 7 + color: "black" + implicitWidth: row.width + 20 + implicitHeight: 25 + } + RowLayout { + x: 5 + anchors.verticalCenter: parent.verticalCenter + id: row + spacing: 15 + Repeater { + model: root2.modelData.toplevels + delegate: Item { + required property HyprlandToplevel modelData + width: icon.implicitWidth + height: icon.implicitHeight + Text { + id: icon + color: "white" + font.pointSize: 13 + // long-ass oneliner for "if there is no class icon, go to fallback" + text: (Common.Icons.appIcons[(modelData.lastIpcObject.class)] != undefined ) ? Common.Icons.appIcons[(modelData.lastIpcObject.class)] : Common.Icons.appIcons["fallback"] + } + } + } + } + } + Item { + width: 30 + height: 30 + MouseArea { + width: 30 + height: 30 + onPressed: event => { + modelData.activate(); + } + } + Rectangle { + width: 30 + height: 30 + radius: 10 + color: modelData.focused ? "grey" : "black" + } + Text { + anchors.centerIn: parent + text: modelData.id + color: "white" + } + } + } + } + Timer { + id: updateTimer + running: true + interval: 1000 // 10secs + repeat: true + onTriggered: { + Hyprland.refreshToplevels() + } + } +}