diff options
| -rw-r--r-- | components/controls/FilledSlider.qml (renamed from components/controls/VerticalSlider.qml) | 2 | ||||
| -rw-r--r-- | components/controls/StyledRadioButton.qml | 58 | ||||
| -rw-r--r-- | components/controls/StyledSlider.qml | 59 | ||||
| -rw-r--r-- | modules/bar/components/StatusIcons.qml | 2 | ||||
| -rw-r--r-- | modules/bar/popouts/Audio.qml | 145 | ||||
| -rw-r--r-- | modules/dashboard/Media.qml | 54 | ||||
| -rw-r--r-- | modules/lock/MediaPlaying.qml | 53 | ||||
| -rw-r--r-- | modules/osd/Content.qml | 15 | ||||
| -rw-r--r-- | services/Audio.qml | 27 | ||||
| -rw-r--r-- | utils/Icons.qml | 10 |
10 files changed, 274 insertions, 151 deletions
diff --git a/components/controls/VerticalSlider.qml b/components/controls/FilledSlider.qml index 306cc24..8d0e547 100644 --- a/components/controls/VerticalSlider.qml +++ b/components/controls/FilledSlider.qml @@ -25,7 +25,7 @@ Slider { implicitHeight: parent.height - y color: Colours.alpha(Colours.palette.m3secondary, true) - radius: Appearance.rounding.full + radius: parent.radius } } diff --git a/components/controls/StyledRadioButton.qml b/components/controls/StyledRadioButton.qml new file mode 100644 index 0000000..73fc836 --- /dev/null +++ b/components/controls/StyledRadioButton.qml @@ -0,0 +1,58 @@ +import qs.components +import qs.services +import qs.config +import QtQuick +import QtQuick.Controls + +RadioButton { + id: root + + font.pointSize: Appearance.font.size.smaller + + indicator: Rectangle { + id: outerCircle + + implicitWidth: 20 + implicitHeight: 20 + radius: Appearance.rounding.full + color: "transparent" + border.color: root.checked ? Colours.palette.m3primary : Colours.palette.m3onSurfaceVariant + border.width: 2 + anchors.verticalCenter: parent.verticalCenter + + StateLayer { + anchors.margins: -Appearance.padding.smaller + color: root.checked ? Colours.palette.m3onSurface : Colours.palette.m3primary + z: -1 + + function onClicked(): void { + root.click(); + } + } + + StyledRect { + anchors.centerIn: parent + implicitWidth: 8 + implicitHeight: 8 + + radius: Appearance.rounding.full + color: root.checked ? Colours.palette.m3primary : "transparent" + } + + Behavior on border.color { + ColorAnimation { + duration: Appearance.anim.durations.normal + easing.type: Easing.BezierSpline + easing.bezierCurve: Appearance.anim.curves.standard + } + } + } + + contentItem: StyledText { + text: root.text + font.pointSize: root.font.pointSize + anchors.verticalCenter: parent.verticalCenter + anchors.left: outerCircle.right + anchors.leftMargin: Appearance.spacing.smaller + } +} diff --git a/components/controls/StyledSlider.qml b/components/controls/StyledSlider.qml new file mode 100644 index 0000000..0352cef --- /dev/null +++ b/components/controls/StyledSlider.qml @@ -0,0 +1,59 @@ +import qs.components +import qs.config +import qs.services +import QtQuick.Controls +import QtQuick + +Slider { + id: slider + + background: Item { + StyledRect { + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.topMargin: slider.implicitHeight / 3 + anchors.bottomMargin: slider.implicitHeight / 3 + + implicitWidth: slider.handle.x - slider.implicitHeight / 6 + + color: Colours.palette.m3primary + radius: Appearance.rounding.full + topRightRadius: slider.implicitHeight / 15 + bottomRightRadius: slider.implicitHeight / 15 + } + + StyledRect { + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.topMargin: slider.implicitHeight / 3 + anchors.bottomMargin: slider.implicitHeight / 3 + + implicitWidth: parent.width - slider.handle.x - slider.handle.implicitWidth - slider.implicitHeight / 6 + + color: Colours.palette.m3surfaceContainer + radius: Appearance.rounding.full + topLeftRadius: slider.implicitHeight / 15 + bottomLeftRadius: slider.implicitHeight / 15 + } + } + + handle: StyledRect { + id: rect + + x: slider.visualPosition * slider.availableWidth + + implicitWidth: slider.implicitHeight / 4.5 + implicitHeight: slider.implicitHeight + + color: Colours.palette.m3primary + radius: Appearance.rounding.full + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onPressed: event => event.accepted = false + } + } +} diff --git a/modules/bar/components/StatusIcons.qml b/modules/bar/components/StatusIcons.qml index ad8c35e..c364d6b 100644 --- a/modules/bar/components/StatusIcons.qml +++ b/modules/bar/components/StatusIcons.qml @@ -58,7 +58,7 @@ Item { sourceComponent: MaterialIcon { animate: true - text: Audio.muted ? "volume_off" : Audio.volume >= 0.66 ? "volume_up" : Audio.volume >= 0.33 ? "volume_down" : "volume_mute" + text: Icons.getVolumeIcon(Audio.volume, Audio.muted) color: root.colour } } diff --git a/modules/bar/popouts/Audio.qml b/modules/bar/popouts/Audio.qml index e6e5514..84378c9 100644 --- a/modules/bar/popouts/Audio.qml +++ b/modules/bar/popouts/Audio.qml @@ -1,58 +1,139 @@ +pragma ComponentBehavior: Bound + import qs.components import qs.components.controls import qs.services import qs.config -import QtQuick.Layouts import Quickshell +import Quickshell.Services.Pipewire +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls -ColumnLayout { +Item { id: root required property var wrapper - spacing: Appearance.spacing.normal + implicitWidth: layout.implicitWidth + Appearance.padding.normal * 2 + implicitHeight: layout.implicitHeight + Appearance.padding.normal * 2 + + ButtonGroup { + id: sinks + } + + ButtonGroup { + id: sources + } + + ColumnLayout { + id: layout - VerticalSlider { - id: volumeSlider + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + spacing: 0 - icon: { - if (Audio.muted) - return "no_sound"; - if (value >= 0.5) - return "volume_up"; - if (value > 0) - return "volume_down"; - return "volume_mute"; + StyledText { + Layout.bottomMargin: Appearance.spacing.small / 2 + text: qsTr("Output") + font.weight: 500 } - value: Audio.volume - onMoved: Audio.setVolume(value) + Repeater { + model: Audio.sinks - implicitWidth: Config.osd.sizes.sliderWidth - implicitHeight: Config.osd.sizes.sliderHeight - } + StyledRadioButton { + id: control + + required property PwNode modelData + + ButtonGroup.group: sinks + checked: Audio.sink?.id === modelData.id + onClicked: Audio.setAudioSink(modelData) + text: modelData.description + } + } + + StyledText { + Layout.topMargin: Appearance.spacing.small + Layout.bottomMargin: Appearance.spacing.small / 2 + text: qsTr("Input") + font.weight: 500 + } - StyledRect { - id: pavuButton + Repeater { + model: Audio.sources - implicitWidth: implicitHeight - implicitHeight: icon.implicitHeight + Appearance.padding.small * 2 + StyledRadioButton { + required property PwNode modelData - radius: Appearance.rounding.normal - color: Colours.palette.m3surfaceContainer + ButtonGroup.group: sources + checked: Audio.source?.id === modelData.id + onClicked: Audio.setAudioSource(modelData) + text: modelData.description + } + } + + StyledText { + Layout.topMargin: Appearance.spacing.small + Layout.bottomMargin: Appearance.spacing.small / 2 + text: qsTr("Volume") + font.weight: 500 + } - StateLayer { - function onClicked(): void { - root.wrapper.hasCurrent = false; - Quickshell.execDetached(["app2unit", "--", ...Config.general.apps.audio]); + StyledSlider { + Layout.fillWidth: true + implicitHeight: Appearance.padding.normal * 3 + + value: Audio.volume + onMoved: Audio.setVolume(value) + + Behavior on value { + NumberAnimation { + duration: Appearance.anim.durations.normal + easing.type: Easing.BezierSpline + easing.bezierCurve: Appearance.anim.curves.standard + } } } - MaterialIcon { - id: icon + StyledRect { + Layout.topMargin: Appearance.spacing.normal + visible: Config.general.apps.audio.length > 0 + + implicitWidth: expandBtn.implicitWidth + Appearance.padding.normal * 2 + implicitHeight: expandBtn.implicitHeight + Appearance.padding.small + + radius: Appearance.rounding.normal + color: Colours.palette.m3primaryContainer - anchors.centerIn: parent - text: "settings" + StateLayer { + color: Colours.palette.m3onPrimaryContainer + + function onClicked(): void { + root.wrapper.hasCurrent = false; + Quickshell.execDetached(["app2unit", "--", ...Config.general.apps.audio]); + } + } + + RowLayout { + id: expandBtn + + anchors.centerIn: parent + spacing: Appearance.spacing.small + + StyledText { + Layout.leftMargin: Appearance.padding.smaller + text: qsTr("Open settings") + color: Colours.palette.m3onPrimaryContainer + } + + MaterialIcon { + text: "chevron_right" + color: Colours.palette.m3onPrimaryContainer + font.pointSize: Appearance.font.size.large + } + } } } } diff --git a/modules/dashboard/Media.qml b/modules/dashboard/Media.qml index c12db22..edb09af 100644 --- a/modules/dashboard/Media.qml +++ b/modules/dashboard/Media.qml @@ -3,6 +3,7 @@ pragma ComponentBehavior: Bound import qs.components import qs.components.effects import qs.components.misc +import qs.components.controls import qs.services import qs.utils import qs.config @@ -279,9 +280,10 @@ Item { } } - Slider { + StyledSlider { id: slider + enabled: !!Players.active implicitWidth: controls.implicitWidth * 1.5 implicitHeight: Appearance.padding.normal * 3 @@ -291,56 +293,6 @@ Item { if (active?.canSeek && active?.positionSupported) active.position = value * active.length; } - - background: Item { - StyledRect { - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.topMargin: slider.implicitHeight / 3 - anchors.bottomMargin: slider.implicitHeight / 3 - - implicitWidth: slider.handle.x - slider.implicitHeight / 6 - - color: Colours.palette.m3primary - radius: Appearance.rounding.full - topRightRadius: slider.implicitHeight / 15 - bottomRightRadius: slider.implicitHeight / 15 - } - - StyledRect { - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.right: parent.right - anchors.topMargin: slider.implicitHeight / 3 - anchors.bottomMargin: slider.implicitHeight / 3 - - implicitWidth: parent.width - slider.handle.x - slider.handle.implicitWidth - slider.implicitHeight / 6 - - color: Colours.palette.m3surfaceContainer - radius: Appearance.rounding.full - topLeftRadius: slider.implicitHeight / 15 - bottomLeftRadius: slider.implicitHeight / 15 - } - } - - handle: StyledRect { - id: rect - - x: slider.visualPosition * slider.availableWidth - - implicitWidth: slider.implicitHeight / 4.5 - implicitHeight: slider.implicitHeight - - color: Colours.palette.m3primary - radius: Appearance.rounding.full - - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onPressed: event => event.accepted = false - } - } } Item { diff --git a/modules/lock/MediaPlaying.qml b/modules/lock/MediaPlaying.qml index 08f1490..2ebd177 100644 --- a/modules/lock/MediaPlaying.qml +++ b/modules/lock/MediaPlaying.qml @@ -1,4 +1,5 @@ import qs.components +import qs.components.controls import qs.services import qs.config import Quickshell.Widgets @@ -164,7 +165,7 @@ RowLayout { spacing: Appearance.spacing.small - Slider { + StyledSlider { id: slider Layout.rightMargin: root.isLarge ? Appearance.spacing.small : 0 @@ -177,56 +178,6 @@ RowLayout { if (active?.canSeek && active?.positionSupported) active.position = value * active.length; } - - background: Item { - StyledRect { - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.topMargin: slider.implicitHeight / 3 - anchors.bottomMargin: slider.implicitHeight / 3 - - implicitWidth: slider.handle.x - slider.implicitHeight / 6 - - color: Colours.palette.m3primary - radius: Appearance.rounding.full - topRightRadius: slider.implicitHeight / 15 - bottomRightRadius: slider.implicitHeight / 15 - } - - StyledRect { - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.right: parent.right - anchors.topMargin: slider.implicitHeight / 3 - anchors.bottomMargin: slider.implicitHeight / 3 - - implicitWidth: parent.width - slider.handle.x - slider.handle.implicitWidth - slider.implicitHeight / 6 - - color: Colours.palette.m3surfaceContainer - radius: Appearance.rounding.full - topLeftRadius: slider.implicitHeight / 15 - bottomLeftRadius: slider.implicitHeight / 15 - } - } - - handle: StyledRect { - id: rect - - x: slider.visualPosition * slider.availableWidth - - implicitWidth: slider.implicitHeight / 4.5 - implicitHeight: slider.implicitHeight - - color: Colours.palette.m3primary - radius: Appearance.rounding.full - - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onPressed: event => event.accepted = false - } - } } Control { diff --git a/modules/osd/Content.qml b/modules/osd/Content.qml index 3177ee9..29b1b39 100644 --- a/modules/osd/Content.qml +++ b/modules/osd/Content.qml @@ -1,6 +1,7 @@ import qs.components.controls import qs.services import qs.config +import qs.utils import QtQuick Column { @@ -26,18 +27,10 @@ Column { Audio.setVolume(Audio.volume - 0.1); } - VerticalSlider { + FilledSlider { anchors.fill: parent - icon: { - if (Audio.muted) - return "no_sound"; - if (value >= 0.5) - return "volume_up"; - if (value > 0) - return "volume_down"; - return "volume_mute"; - } + icon: Icons.getVolumeIcon(value, Audio.muted) value: Audio.volume onMoved: Audio.setVolume(value) } @@ -57,7 +50,7 @@ Column { monitor.setBrightness(monitor.brightness - 0.1); } - VerticalSlider { + FilledSlider { anchors.fill: parent icon: `brightness_${(Math.round(value * 6) + 1)}` diff --git a/services/Audio.qml b/services/Audio.qml index c91adbc..54cea3b 100644 --- a/services/Audio.qml +++ b/services/Audio.qml @@ -6,20 +6,39 @@ import Quickshell.Services.Pipewire Singleton { id: root + readonly property var nodes: Pipewire.nodes.values.reduce((acc, node) => { + if (!node.isStream) { + if (node.isSink) acc.sinks.push(node) + else if (node.audio) acc.sources.push(node) + } + return acc + }, { sources: [], sinks: [] }) + + readonly property list<PwNode> sinks: nodes.sinks + readonly property list<PwNode> sources: nodes.sources + readonly property PwNode sink: Pipewire.defaultAudioSink readonly property PwNode source: Pipewire.defaultAudioSource - readonly property bool muted: sink?.audio?.muted ?? false + readonly property bool muted: !!sink?.audio?.muted readonly property real volume: sink?.audio?.volume ?? 0 - function setVolume(volume: real): void { + function setVolume(newVolume: real): void { if (sink?.ready && sink?.audio) { sink.audio.muted = false; - sink.audio.volume = volume; + sink.audio.volume = newVolume; } } + function setAudioSink(newSink: PwNode): void { + Pipewire.preferredDefaultAudioSink = newSink + } + + function setAudioSource(newSource: PwNode): void { + Pipewire.preferredDefaultAudioSource = newSource + } + PwObjectTracker { - objects: [Pipewire.defaultAudioSink, Pipewire.defaultAudioSource] + objects: [...root.sinks, ...root.sources] } } diff --git a/utils/Icons.qml b/utils/Icons.qml index 5e95575..cd635a0 100644 --- a/utils/Icons.qml +++ b/utils/Icons.qml @@ -223,6 +223,16 @@ Singleton { return "chat"; } + function getVolumeIcon(volume: real, isMuted: bool): string { + if (isMuted) + return "no_sound"; + if (volume >= 0.5) + return "volume_up"; + if (volume > 0) + return "volume_down"; + return "volume_mute"; + } + FileView { path: "/etc/os-release" onLoaded: { |