test new systray

This commit is contained in:
2026-04-07 00:06:21 +02:00
parent 440d103b46
commit e5c03448c2
14 changed files with 216 additions and 216 deletions
+520
View File
@@ -0,0 +1,520 @@
pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Services.Pipewire
import Quickshell.Widgets
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import qs.Config
import qs.Components
import qs.Daemons
import qs.Helpers
Item {
id: root
readonly property int rounding: Appearance.rounding.small - Appearance.padding.small
readonly property int topMargin: 0
required property var wrapper
implicitHeight: layout.implicitHeight + Appearance.padding.small * 2
implicitWidth: layout.implicitWidth + Appearance.padding.small * 2
CustomRect {
anchors.left: parent.left
anchors.right: parent.right
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: parent.implicitHeight
radius: Appearance.rounding.small
y: parent.y
Behavior on implicitHeight {
Anim {
duration: MaterialEasing.emphasizedDecelTime
easing.bezierCurve: MaterialEasing.emphasized
}
}
}
ColumnLayout {
id: layout
anchors.centerIn: parent
implicitWidth: stack.currentItem ? stack.currentItem.childrenRect.height : 0
spacing: 12
RowLayout {
id: tabBar
property int tabHeight: 36
Layout.fillWidth: true
spacing: 6
CustomClippingRect {
Layout.fillWidth: true
Layout.preferredHeight: tabBar.tabHeight
color: stack.currentIndex === 0 ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3surfaceContainer
radius: root.rounding
StateLayer {
function onClicked(): void {
stack.currentIndex = 0;
}
CustomText {
anchors.centerIn: parent
color: stack.currentIndex === 0 ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3primary
text: qsTr("Volumes")
}
}
}
CustomClippingRect {
Layout.fillWidth: true
Layout.preferredHeight: tabBar.tabHeight
color: stack.currentIndex === 1 ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3surfaceContainer
radius: root.rounding
StateLayer {
function onClicked(): void {
stack.currentIndex = 1;
}
CustomText {
anchors.centerIn: parent
color: stack.currentIndex === 1 ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3primary
text: qsTr("Devices")
}
}
}
}
StackLayout {
id: stack
Layout.fillWidth: true
Layout.preferredHeight: currentIndex === 0 ? vol.childrenRect.height : dev.childrenRect.height
currentIndex: 0
Behavior on currentIndex {
SequentialAnimation {
ParallelAnimation {
Anim {
duration: MaterialEasing.expressiveEffectsTime
property: "opacity"
target: stack
to: 0
}
Anim {
duration: MaterialEasing.expressiveEffectsTime
property: "scale"
target: stack
to: 0.9
}
}
PropertyAction {
}
ParallelAnimation {
Anim {
duration: MaterialEasing.expressiveEffectsTime
property: "opacity"
target: stack
to: 1
}
Anim {
duration: MaterialEasing.expressiveEffectsTime
property: "scale"
target: stack
to: 1
}
}
}
}
VolumesTab {
id: vol
}
DevicesTab {
id: dev
}
}
}
component DevicesTab: Item {
implicitHeight: deviceColumn.height + Appearance.padding.normal
implicitWidth: deviceColumn.width + Appearance.padding.normal
ColumnLayout {
id: deviceColumn
anchors.centerIn: parent
spacing: 12
ButtonGroup {
id: sinks
}
ButtonGroup {
id: sources
}
CustomText {
font.weight: 500
text: qsTr("Output device")
}
Repeater {
model: Audio.sinks
CustomRadioButton {
required property PwNode modelData
ButtonGroup.group: sinks
checked: Audio.sink?.id === modelData.id
text: modelData.description
onClicked: Audio.setAudioSink(modelData)
}
}
CustomText {
Layout.topMargin: 10
font.weight: 500
text: qsTr("Input device")
}
Repeater {
model: Audio.sources
CustomRadioButton {
required property PwNode modelData
ButtonGroup.group: sources
checked: Audio.source?.id === modelData.id
text: modelData.description
onClicked: Audio.setAudioSource(modelData)
}
}
}
}
component VolumesTab: ColumnLayout {
spacing: 12
CustomRect {
Layout.fillWidth: true
Layout.preferredHeight: 42 + Appearance.spacing.smaller * 2
Layout.topMargin: root.topMargin
color: DynamicColors.tPalette.m3surfaceContainer
radius: root.rounding
RowLayout {
id: outputVolume
anchors.left: parent.left
anchors.margins: Appearance.spacing.smaller
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: 15
CustomRect {
Layout.alignment: Qt.AlignVCenter
Layout.preferredHeight: 40
Layout.preferredWidth: 40
color: Audio.muted ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary
radius: Appearance.rounding.full
MaterialIcon {
anchors.alignWhenCentered: false
anchors.centerIn: parent
animate: true
color: Audio.muted ? DynamicColors.palette.m3onError : DynamicColors.palette.m3onPrimary
font.pointSize: 22
text: Audio.muted ? "volume_off" : "volume_up"
}
StateLayer {
color: Audio.muted ? DynamicColors.palette.m3onError : DynamicColors.palette.m3onPrimary
onClicked: {
const audio = Audio.sink?.audio;
if (audio)
audio.muted = !audio.muted;
}
}
}
ColumnLayout {
Layout.fillWidth: true
RowLayout {
Layout.fillWidth: true
CustomText {
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
Layout.fillWidth: true
text: "Output Volume"
}
CustomText {
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
font.bold: true
text: qsTr("%1").arg(Audio.muted ? qsTr("Muted") : `${Math.round(Audio.volume * 100)}%`)
}
}
CustomMouseArea {
Layout.bottomMargin: 5
Layout.fillWidth: true
Layout.preferredHeight: 10
CustomSlider {
anchors.left: parent.left
anchors.right: parent.right
color: Audio.muted ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary
implicitHeight: 10
value: Audio.volume
Behavior on value {
Anim {
}
}
onMoved: Audio.setVolume(value)
}
}
}
}
}
CustomRect {
Layout.fillWidth: true
Layout.preferredHeight: 42 + Appearance.spacing.smaller * 2
Layout.topMargin: root.topMargin
color: DynamicColors.tPalette.m3surfaceContainer
radius: root.rounding
RowLayout {
id: inputVolume
anchors.left: parent.left
anchors.margins: Appearance.spacing.smaller
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: 15
CustomRect {
Layout.alignment: Qt.AlignVCenter
Layout.preferredHeight: 40
Layout.preferredWidth: 40
color: Audio.sourceMuted ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary
radius: Appearance.rounding.full
MaterialIcon {
anchors.alignWhenCentered: false
anchors.centerIn: parent
animate: true
color: Audio.sourceMuted ? DynamicColors.palette.m3onError : DynamicColors.palette.m3onPrimary
font.pointSize: 22
text: Audio.sourceMuted ? "mic_off" : "mic"
}
StateLayer {
color: Audio.sourceMuted ? DynamicColors.palette.m3onError : DynamicColors.palette.m3onPrimary
onClicked: {
const audio = Audio.source?.audio;
if (audio)
audio.muted = !audio.muted;
}
}
}
ColumnLayout {
Layout.fillWidth: true
RowLayout {
Layout.fillHeight: true
Layout.fillWidth: true
CustomText {
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
Layout.fillWidth: true
text: "Input Volume"
}
CustomText {
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
font.bold: true
text: qsTr("%1").arg(Audio.sourceMuted ? qsTr("Muted") : `${Math.round(Audio.sourceVolume * 100)}%`)
}
}
CustomMouseArea {
Layout.bottomMargin: 5
Layout.fillWidth: true
implicitHeight: 10
CustomSlider {
anchors.left: parent.left
anchors.right: parent.right
color: Audio.sourceMuted ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary
implicitHeight: 10
value: Audio.sourceVolume
Behavior on value {
Anim {
}
}
onMoved: Audio.setSourceVolume(value)
}
}
}
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
Layout.topMargin: root.topMargin
color: DynamicColors.tPalette.m3outline
visible: appTracks.model.length > 0
}
Repeater {
id: appTracks
model: Audio.streams.filter(s => s.isSink)
CustomClippingRect {
id: appBox
required property int index
required property var modelData
Layout.fillWidth: true
Layout.preferredHeight: 42 + Appearance.spacing.smaller * 2
Layout.topMargin: root.topMargin
color: DynamicColors.tPalette.m3surfaceContainer
radius: root.rounding
PwNodePeakMonitor {
id: peak
node: appBox.modelData
}
CustomRect {
id: peakFill
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.top: parent.top
color: Qt.alpha(DynamicColors.palette.m3primary, 0.15)
implicitWidth: parent.width * peak.peak
Behavior on implicitWidth {
Anim {
duration: MaterialEasing.expressiveEffectsTime
}
}
}
RowLayout {
id: layoutVolume
anchors.fill: parent
anchors.margins: Appearance.spacing.smaller
spacing: 15
CustomRect {
Layout.alignment: Qt.AlignVCenter
Layout.preferredHeight: 40
Layout.preferredWidth: 40
color: appBox.modelData.audio.muted ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary
radius: Appearance.rounding.full
MaterialIcon {
id: icon
anchors.centerIn: parent
animate: true
color: appBox.modelData.audio.muted ? DynamicColors.palette.m3onError : DynamicColors.palette.m3onPrimary
font.pointSize: 22
text: appBox.modelData.audio.muted ? "volume_off" : "volume_up"
}
StateLayer {
color: appBox.modelData.audio.muted ? DynamicColors.palette.m3onError : DynamicColors.palette.m3onPrimary
radius: Appearance.rounding.full
onClicked: {
appBox.modelData.audio.muted = !appBox.modelData.audio.muted;
}
}
}
ColumnLayout {
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillHeight: true
TextMetrics {
id: metrics
elide: Text.ElideRight
elideWidth: root.width - 50
text: Audio.getStreamName(appBox.modelData)
}
RowLayout {
Layout.fillHeight: true
Layout.fillWidth: true
CustomText {
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
Layout.fillHeight: true
Layout.fillWidth: true
elide: Text.ElideRight
text: metrics.elidedText
}
CustomText {
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
Layout.fillHeight: true
font.bold: true
text: qsTr("%1").arg(appBox.modelData.audio.muted ? qsTr("Muted") : `${Math.round(appBox.modelData.audio.volume * 100)}%`)
}
}
CustomMouseArea {
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
Layout.fillHeight: true
Layout.fillWidth: true
implicitHeight: 10
CustomSlider {
anchors.left: parent.left
anchors.right: parent.right
color: appBox.modelData.audio.muted ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary
implicitHeight: 10
value: appBox.modelData.audio.volume
onMoved: {
Audio.setStreamVolume(appBox.modelData, value);
}
}
}
}
}
}
}
}
}
+248
View File
@@ -0,0 +1,248 @@
pragma ComponentBehavior: Bound
import qs.Components
import qs.Config
import Quickshell
import Quickshell.Widgets
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
StackView {
id: root
property int biggestWidth: 0
required property Item popouts
property int rootWidth: 0
required property QsMenuHandle trayItem
implicitHeight: currentItem.implicitHeight
implicitWidth: currentItem.implicitWidth
initialItem: SubMenu {
handle: root.trayItem
}
popEnter: NoAnim {
}
popExit: NoAnim {
}
pushEnter: NoAnim {
}
pushExit: NoAnim {
}
Component {
id: subMenuComp
SubMenu {
}
}
component NoAnim: Transition {
NumberAnimation {
duration: 0
}
}
component SubMenu: Column {
id: menu
required property QsMenuHandle handle
property bool isSubMenu
property bool shown
opacity: shown ? 1 : 0
padding: 0
scale: shown ? 1 : 0.8
spacing: 4
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Component.onCompleted: shown = true
StackView.onActivating: shown = true
StackView.onDeactivating: shown = false
StackView.onRemoved: destroy()
QsMenuOpener {
id: menuOpener
menu: menu.handle
}
Repeater {
model: menuOpener.children
CustomRect {
id: item
required property QsMenuEntry modelData
color: modelData.isSeparator ? DynamicColors.palette.m3outlineVariant : "transparent"
implicitHeight: modelData.isSeparator ? 1 : children.implicitHeight
implicitWidth: root.biggestWidth
radius: Appearance.rounding.smallest / 2
Loader {
id: children
active: !item.modelData.isSeparator
anchors.left: parent.left
anchors.right: parent.right
asynchronous: true
sourceComponent: Item {
implicitHeight: 30
StateLayer {
function onClicked(): void {
const entry = item.modelData;
if (entry.hasChildren) {
root.rootWidth = root.biggestWidth;
root.biggestWidth = 0;
root.push(subMenuComp.createObject(null, {
handle: entry,
isSubMenu: true
}));
} else {
item.modelData.triggered();
root.popouts.hasCurrent = false;
}
}
disabled: !item.modelData.enabled
radius: item.radius
}
Loader {
id: icon
active: item.modelData.icon !== ""
anchors.right: parent.right
anchors.rightMargin: 10
anchors.verticalCenter: parent.verticalCenter
asynchronous: true
sourceComponent: Item {
implicitHeight: label.implicitHeight
implicitWidth: label.implicitHeight
IconImage {
id: iconImage
implicitSize: parent.implicitHeight
source: item.modelData.icon
visible: false
}
MultiEffect {
anchors.fill: iconImage
colorization: 1.0
colorizationColor: item.modelData.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3outline
source: iconImage
}
}
}
CustomText {
id: label
anchors.left: parent.left
anchors.leftMargin: 10
anchors.verticalCenter: parent.verticalCenter
color: item.modelData.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3outline
text: labelMetrics.elidedText
}
TextMetrics {
id: labelMetrics
font.family: label.font.family
font.pointSize: label.font.pointSize
text: item.modelData.text
Component.onCompleted: {
var biggestWidth = root.biggestWidth;
var currentWidth = labelMetrics.width + (item.modelData.icon ?? "" ? 30 : 0) + (item.modelData.hasChildren ? 30 : 0) + 20;
if (currentWidth > biggestWidth) {
root.biggestWidth = currentWidth;
}
}
}
Loader {
id: expand
active: item.modelData.hasChildren
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
asynchronous: true
sourceComponent: MaterialIcon {
color: item.modelData.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3outline
text: "chevron_right"
}
}
}
}
}
}
Loader {
id: loader
active: menu.isSubMenu
asynchronous: true
sourceComponent: Item {
implicitHeight: back.implicitHeight + 2 / 2
implicitWidth: back.implicitWidth
Item {
anchors.bottom: parent.bottom
implicitHeight: back.implicitHeight
implicitWidth: back.implicitWidth + 10
CustomRect {
anchors.fill: parent
color: DynamicColors.palette.m3secondaryContainer
radius: Appearance.rounding.smallest / 2
StateLayer {
function onClicked(): void {
root.pop();
root.biggestWidth = root.rootWidth;
}
color: DynamicColors.palette.m3onSecondaryContainer
radius: parent.radius
}
}
Row {
id: back
anchors.verticalCenter: parent.verticalCenter
MaterialIcon {
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.palette.m3onSecondaryContainer
text: "chevron_left"
}
CustomText {
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.palette.m3onSecondaryContainer
text: qsTr("Back")
}
}
}
}
}
}
}
+179
View File
@@ -0,0 +1,179 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell.Services.UPower
import qs.Config
import qs.Components
import qs.Modules
Item {
id: root
required property var wrapper
implicitHeight: profiles.implicitHeight
implicitWidth: profiles.implicitWidth
CustomRect {
id: profiles
property string current: {
const p = PowerProfiles.profile;
if (p === PowerProfile.PowerSaver)
return saver.icon;
if (p === PowerProfile.Performance)
return perf.icon;
return balance.icon;
}
anchors.horizontalCenter: parent.horizontalCenter
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: Math.max(saver.implicitHeight, balance.implicitHeight, perf.implicitHeight) + 5 * 2 + saverLabel.contentHeight
implicitWidth: saver.implicitHeight + balance.implicitHeight + perf.implicitHeight + 8 * 2 + saverLabel.contentWidth
// color: "transparent"
radius: 6
CustomRect {
id: indicator
color: DynamicColors.palette.m3primary
radius: Appearance.rounding.full
state: profiles.current
states: [
State {
name: saver.icon
Fill {
item: saver
}
},
State {
name: balance.icon
Fill {
item: balance
}
},
State {
name: perf.icon
Fill {
item: perf
}
}
]
transitions: Transition {
AnchorAnimation {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
easing.type: Easing.BezierSpline
}
}
}
Profile {
id: saver
anchors.left: parent.left
anchors.leftMargin: 25
anchors.top: parent.top
anchors.topMargin: 8
icon: "nest_eco_leaf"
profile: PowerProfile.PowerSaver
text: "Power Saver"
}
CustomText {
id: saverLabel
anchors.horizontalCenter: saver.horizontalCenter
anchors.top: saver.bottom
font.bold: true
text: saver.text
}
Profile {
id: balance
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 8
icon: "power_settings_new"
profile: PowerProfile.Balanced
text: "Balanced"
}
CustomText {
id: balanceLabel
anchors.horizontalCenter: balance.horizontalCenter
anchors.top: balance.bottom
font.bold: true
text: balance.text
}
Profile {
id: perf
anchors.right: parent.right
anchors.rightMargin: 25
anchors.top: parent.top
anchors.topMargin: 8
icon: "bolt"
profile: PowerProfile.Performance
text: "Performance"
}
CustomText {
id: perfLabel
anchors.horizontalCenter: perf.horizontalCenter
anchors.top: perf.bottom
font.bold: true
text: perf.text
}
}
component Fill: AnchorChanges {
required property Item item
anchors.bottom: item.bottom
anchors.left: item.left
anchors.right: item.right
anchors.top: item.top
target: indicator
}
component Profile: Item {
required property string icon
required property int profile
required property string text
implicitHeight: icon.implicitHeight + 5 * 2
implicitWidth: icon.implicitHeight + 5 * 2
StateLayer {
function onClicked(): void {
PowerProfiles.profile = parent.profile;
}
color: profiles.current === parent.icon ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
radius: Appearance.rounding.full
}
MaterialIcon {
id: icon
anchors.centerIn: parent
color: profiles.current === text ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
fill: profiles.current === text ? 1 : 0
font.pointSize: Appearance.font.size.large * 2
text: parent.icon
Behavior on fill {
Anim {
}
}
}
}
}
+55
View File
@@ -0,0 +1,55 @@
import QtQuick.Layouts
import QtQuick
import Quickshell
import Quickshell.Services.SystemTray
import qs.Modules
import qs.Components
import qs.Config
Item {
id: root
property bool current: popouts.currentName.startsWith(`traymenu${ind}`) && popouts.hasCurrent
property bool hasLoaded: false
required property int ind
required property SystemTrayItem item
required property RowLayout loader
required property Wrapper popouts
CustomRect {
anchors.fill: parent
anchors.margins: 3
color: root.current ? DynamicColors.palette.m3primary : "transparent"
radius: Appearance.rounding.full
StateLayer {
acceptedButtons: Qt.LeftButton | Qt.RightButton
anchors.fill: parent
onClicked: {
if (mouse.button === Qt.LeftButton) {
root.item.activate();
} else if (mouse.button === Qt.RightButton) {
root.popouts.currentName = `traymenu${root.ind}`;
root.popouts.currentCenter = Qt.binding(() => root.mapToItem(root.loader, root.implicitWidth / 2, 0).x);
root.popouts.hasCurrent = true;
if (visibilities.sidebar || visibilities.dashboard || visibilities.settings) {
visibilities.sidebar = false;
visibilities.dashboard = false;
visibilities.settings = false;
}
}
}
}
}
ColoredIcon {
id: icon
anchors.centerIn: parent
color: root.current ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
implicitSize: 22
layer.enabled: Config.general.color.smart || Config.general.color.scheduleDark
source: root.item.icon
}
}
+122
View File
@@ -0,0 +1,122 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Services.SystemTray
import qs.Components
import qs.Config
import qs.Modules.SysTray.Widgets
import qs.Modules.SysTray.Popouts
import qs.Modules
RowLayout {
id: root
readonly property alias items: repeater
required property RowLayout loader
required property Wrapper popouts
function getHoveredSubItem(localX, localY) {
let modPos = mapToItem(sysTrayMod, localX, localY);
if (sysTrayMod.contains(Qt.point(modPos.x, modPos.y))) {
let modRowPos = sysTrayMod.mapToItem(sysModRow, modPos.x, modPos.y);
let child = sysModRow.childAt(modRowPos.x, modRowPos.y);
if (child) {
if (child.objectName === "audioWidget")
return {
id: "audio",
item: child
};
if (child.objectName === "upowerWidget")
return {
id: "upower",
item: child
};
}
}
let trayPos = mapToItem(sysTray, localX, localY);
if (sysTray.contains(Qt.point(trayPos.x, trayPos.y))) {
let trayRowPos = sysTray.mapToItem(sysRow, trayPos.x, trayPos.y);
let child = sysRow.childAt(trayRowPos.x, trayRowPos.y);
if (child && child.hasOwnProperty("popoutId")) {
return {
id: child.popoutId,
item: child
};
}
}
return null;
}
height: Config.barConfig.height + Appearance.padding.smallest * 2
spacing: Appearance.padding.small
width: sysTray.implicitWidth + sysTrayMod.implicitWidth + Appearance.padding.small
CustomClippingRect {
id: sysTray
Layout.fillHeight: true
bottomRightRadius: Appearance.rounding.smallest / 2
color: DynamicColors.tPalette.m3surfaceContainer
implicitWidth: sysRow.width + Appearance.padding.small * 2
radius: Appearance.rounding.full
topRightRadius: Appearance.rounding.smallest / 2
Row {
id: sysRow
anchors.centerIn: parent
spacing: 0
Repeater {
id: repeater
model: SystemTray.items
TrayItem {
id: trayItem
required property int index
required property SystemTrayItem modelData
implicitHeight: 34
implicitWidth: 34
ind: index
item: modelData
loader: root.loader
popouts: root.popouts
}
}
}
}
CustomClippingRect {
id: sysTrayMod
Layout.fillHeight: true
bottomLeftRadius: Appearance.rounding.smallest / 2
color: DynamicColors.tPalette.m3surfaceContainer
implicitWidth: sysModRow.width + Appearance.padding.smaller * 2
radius: Appearance.rounding.full
topLeftRadius: Appearance.rounding.smallest / 2
Row {
id: sysModRow
anchors.centerIn: parent
spacing: Appearance.padding.small
AudioWidget {
objectName: "audioWidget"
}
UPowerWidget {
height: parent.height
objectName: "upowerWidget"
}
}
}
}
+31
View File
@@ -0,0 +1,31 @@
import QtQuick
import QtQuick.Layouts
import Quickshell.Io
import Quickshell.Services.Pipewire
import qs.Daemons
import qs.Modules
import qs.Config
import qs.Components
RowLayout {
id: root
property color barColor: DynamicColors.palette.m3primary
property color textColor: DynamicColors.palette.m3onSurface
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
animate: true
color: (Audio.sourceMuted ?? false) ? DynamicColors.palette.m3error : root.textColor
font.pointSize: Appearance.font.size.larger
text: Audio.sourceMuted ? "mic_off" : "mic"
}
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
animate: true
color: Audio.muted ? DynamicColors.palette.m3error : root.textColor
font.pointSize: Appearance.font.size.larger
text: Audio.muted ? "volume_off" : "volume_up"
}
}
+35
View File
@@ -0,0 +1,35 @@
import Quickshell.Services.UPower
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
import qs.Helpers as Helpers
RowLayout {
id: root
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
animate: true
color: !Helpers.UPower.onBattery || UPower.displayDevice.percentage > 0.2 ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3error
fill: 1
text: {
if (!Helpers.UPower.displayDevice.isLaptopBattery) {
if (PowerProfiles.profile === PowerProfile.PowerSaver)
return "nest_eco_leaf";
if (PowerProfiles.profile === PowerProfile.Performance)
return "bolt";
return "power_settings_new";
}
const perc = Helpers.UPower.displayDevice.percentage;
const charging = [UPowerDeviceState.Charging, UPowerDeviceState.FullyCharged, UPowerDeviceState.PendingCharge].includes(Helpers.UPower.displayDevice.state);
if (perc === 1)
return charging ? "battery_charging_full" : "battery_full";
let level = Math.floor(perc * 7);
if (charging && (level === 4 || level === 1))
level--;
return charging ? `battery_charging_${(level + 3) * 10}` : `battery_${level}_bar`;
}
}
}