mirror of
https://github.com/zoriya/noctalia-shell.git
synced 2025-12-06 06:36:15 +00:00
feat: New audio panel when clicking on volume icon
This commit is contained in:
244
Modules/Bar/Audio/AudioPanel.qml
Normal file
244
Modules/Bar/Audio/AudioPanel.qml
Normal file
@@ -0,0 +1,244 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Services.Pipewire
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
NPanel {
|
||||
id: root
|
||||
|
||||
property real localOutputVolume: AudioService.volume || 0
|
||||
property bool localOutputVolumeChanging: false
|
||||
|
||||
property real localInputVolume: AudioService.inputVolume || 0
|
||||
property bool localInputVolumeChanging: false
|
||||
|
||||
preferredWidth: 380 * Style.uiScaleRatio
|
||||
preferredHeight: 500 * Style.uiScaleRatio
|
||||
panelKeyboardFocus: true
|
||||
|
||||
// Connections to update local volumes when AudioService changes
|
||||
Connections {
|
||||
target: AudioService.sink?.audio ? AudioService.sink?.audio : null
|
||||
function onVolumeChanged() {
|
||||
if (!localOutputVolumeChanging) {
|
||||
localOutputVolume = AudioService.volume
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: AudioService.source?.audio ? AudioService.source?.audio : null
|
||||
function onVolumeChanged() {
|
||||
if (!localInputVolumeChanging) {
|
||||
localInputVolume = AudioService.inputVolume
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Timer to debounce volume changes
|
||||
Timer {
|
||||
interval: 100
|
||||
running: true
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
if (Math.abs(localOutputVolume - AudioService.volume) >= 0.01) {
|
||||
AudioService.setVolume(localOutputVolume)
|
||||
}
|
||||
if (Math.abs(localInputVolume - AudioService.inputVolume) >= 0.01) {
|
||||
AudioService.setInputVolume(localInputVolume)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
panelContent: Rectangle {
|
||||
color: Color.transparent
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginL
|
||||
spacing: Style.marginM
|
||||
|
||||
// HEADER
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
NIcon {
|
||||
icon: "settings-audio"
|
||||
pointSize: Style.fontSizeXXL
|
||||
color: Color.mPrimary
|
||||
}
|
||||
|
||||
NText {
|
||||
text: I18n.tr("settings.audio.title")
|
||||
pointSize: Style.fontSizeL
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: AudioService.getOutputIcon()
|
||||
tooltipText: I18n.tr("tooltips.output-devices")
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: {
|
||||
AudioService.setOutputMuted(!AudioService.muted)
|
||||
}
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: AudioService.getInputIcon()
|
||||
tooltipText: I18n.tr("tooltips.refresh-devices")
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: {
|
||||
AudioService.setInputMuted(!AudioService.inputMuted)
|
||||
}
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "close"
|
||||
tooltipText: I18n.tr("tooltips.close")
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: {
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NScrollView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
horizontalPolicy: ScrollBar.AlwaysOff
|
||||
verticalPolicy: ScrollBar.AsNeeded
|
||||
clip: true
|
||||
contentWidth: availableWidth
|
||||
|
||||
// AudioService Devices
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS
|
||||
Layout.fillWidth: true
|
||||
|
||||
// -------------------------------
|
||||
// Output Devices
|
||||
ButtonGroup {
|
||||
id: sinks
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: Style.marginL
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.spacingM * Style.uiScaling
|
||||
Layout.bottomMargin: Style.marginL
|
||||
|
||||
NText {
|
||||
text: I18n.tr("settings.audio.devices.output-device.label")
|
||||
pointSize: Style.fontSizeL
|
||||
color: Color.mPrimary
|
||||
Layout.preferredWidth: root.preferredWidth * 0.3
|
||||
}
|
||||
|
||||
// Output Volume Slider
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: root.preferredWidth * 0.6
|
||||
from: 0
|
||||
to: Settings.data.audio.volumeOverdrive ? 1.5 : 1.0
|
||||
value: localOutputVolume
|
||||
stepSize: 0.01
|
||||
heightRatio: 0.5
|
||||
onMoved: value => localOutputVolume = value
|
||||
onPressedChanged: (pressed, value) => localOutputVolumeChanging = pressed
|
||||
text: Math.round(localOutputVolume * 100) + "%"
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: AudioService.sinks
|
||||
NRadioButton {
|
||||
ButtonGroup.group: sinks
|
||||
required property PwNode modelData
|
||||
pointSize: Style.fontSizeS
|
||||
text: modelData.description
|
||||
checked: AudioService.sink?.id === modelData.id
|
||||
onClicked: {
|
||||
AudioService.setAudioSink(modelData)
|
||||
localVolume = AudioService.volume
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// -------------------------------
|
||||
// Input Devices
|
||||
ButtonGroup {
|
||||
id: sources
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
Layout.fillWidth: true
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.spacingM * Style.uiScaling
|
||||
Layout.bottomMargin: Style.marginL
|
||||
|
||||
NText {
|
||||
text: I18n.tr("settings.audio.devices.input-device.label")
|
||||
pointSize: Style.fontSizeL
|
||||
color: Color.mPrimary
|
||||
Layout.preferredWidth: root.preferredWidth * 0.3
|
||||
}
|
||||
|
||||
// Input Volume Slider
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: root.preferredWidth * 0.6
|
||||
from: 0
|
||||
to: Settings.data.audio.volumeOverdrive ? 1.5 : 1.0
|
||||
value: localInputVolume
|
||||
stepSize: 0.01
|
||||
heightRatio: 0.5
|
||||
onMoved: value => localInputVolume = value
|
||||
onPressedChanged: (pressed, value) => localInputVolumeChanging = pressed
|
||||
text: Math.round(localInputVolume * 100) + "%"
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: AudioService.sources
|
||||
//Layout.fillWidth: true
|
||||
NRadioButton {
|
||||
ButtonGroup.group: sources
|
||||
required property PwNode modelData
|
||||
pointSize: Style.fontSizeS
|
||||
text: modelData.description
|
||||
checked: AudioService.source?.id === modelData.id
|
||||
onClicked: AudioService.setAudioSource(modelData)
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,13 +40,6 @@ Item {
|
||||
implicitWidth: pill.width
|
||||
implicitHeight: pill.height
|
||||
|
||||
function getIcon() {
|
||||
if (AudioService.inputMuted) {
|
||||
return "microphone-mute"
|
||||
}
|
||||
return (AudioService.inputVolume <= Number.EPSILON) ? "microphone-mute" : "microphone"
|
||||
}
|
||||
|
||||
// Connection used to open the pill when input volume changes
|
||||
Connections {
|
||||
target: AudioService.source?.audio ? AudioService.source?.audio : null
|
||||
@@ -90,7 +83,7 @@ Item {
|
||||
id: pill
|
||||
|
||||
oppositeDirection: BarService.getPillDirection(root)
|
||||
icon: getIcon()
|
||||
icon: AudioService.getInputIcon()
|
||||
density: Settings.data.bar.density
|
||||
autoHide: false // Important to be false so we can hover as long as we want
|
||||
text: Math.round(AudioService.inputVolume * 100)
|
||||
|
||||
@@ -40,13 +40,6 @@ Item {
|
||||
implicitWidth: pill.width
|
||||
implicitHeight: pill.height
|
||||
|
||||
function getIcon() {
|
||||
if (AudioService.muted) {
|
||||
return "volume-mute"
|
||||
}
|
||||
return (AudioService.volume <= Number.EPSILON) ? "volume-zero" : (AudioService.volume <= 0.5) ? "volume-low" : "volume-high"
|
||||
}
|
||||
|
||||
// Connection used to open the pill when volume changes
|
||||
Connections {
|
||||
target: AudioService.sink?.audio ? AudioService.sink?.audio : null
|
||||
@@ -76,7 +69,7 @@ Item {
|
||||
|
||||
density: Settings.data.bar.density
|
||||
oppositeDirection: BarService.getPillDirection(root)
|
||||
icon: getIcon()
|
||||
icon: AudioService.getOutputIcon()
|
||||
autoHide: false // Important to be false so we can hover as long as we want
|
||||
text: Math.round(AudioService.volume * 100)
|
||||
suffix: "%"
|
||||
@@ -100,9 +93,7 @@ Item {
|
||||
AudioService.setOutputMuted(!AudioService.muted)
|
||||
}
|
||||
onRightClicked: {
|
||||
var settingsPanel = PanelService.getPanel("settingsPanel")
|
||||
settingsPanel.requestedTab = SettingsPanel.Tab.Audio
|
||||
settingsPanel.open()
|
||||
PanelService.getPanel("audioPanel")?.toggle(this)
|
||||
}
|
||||
onMiddleClicked: {
|
||||
Quickshell.execDetached(["pwvucontrol"])
|
||||
|
||||
@@ -148,4 +148,18 @@ Singleton {
|
||||
root._inputVolume = newSource?.audio?.volume ?? 0
|
||||
root._inputMuted = !!newSource?.audio?.muted
|
||||
}
|
||||
|
||||
function getOutputIcon() {
|
||||
if (muted) {
|
||||
return "volume-mute"
|
||||
}
|
||||
return (volume <= Number.EPSILON) ? "volume-zero" : (volume <= 0.5) ? "volume-low" : "volume-high"
|
||||
}
|
||||
|
||||
function getInputIcon() {
|
||||
if (inputMuted) {
|
||||
return "microphone-mute"
|
||||
}
|
||||
return (inputVolume <= Number.EPSILON) ? "microphone-mute" : "microphone"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,13 @@ import qs.Widgets
|
||||
RadioButton {
|
||||
id: root
|
||||
|
||||
property real pointSize: Style.fontSizeM
|
||||
|
||||
indicator: Rectangle {
|
||||
id: outerCircle
|
||||
|
||||
implicitWidth: Style.baseWidgetSize * 0.625
|
||||
implicitHeight: Style.baseWidgetSize * 0.625
|
||||
implicitWidth: Style.baseWidgetSize * 0.625 * pointSize / Style.fontSizeM
|
||||
implicitHeight: Style.baseWidgetSize * 0.625 * pointSize / Style.fontSizeM
|
||||
radius: width * 0.5
|
||||
color: Color.transparent
|
||||
border.color: root.checked ? Color.mPrimary : Color.mOnSurface
|
||||
@@ -41,7 +43,7 @@ RadioButton {
|
||||
|
||||
contentItem: NText {
|
||||
text: root.text
|
||||
pointSize: Style.fontSizeM
|
||||
pointSize: root.pointSize
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: outerCircle.right
|
||||
anchors.right: parent.right
|
||||
|
||||
@@ -47,7 +47,7 @@ RowLayout {
|
||||
pointSize: root.textSize
|
||||
family: Settings.data.ui.fontFixed
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredWidth: 45 * Scale.uiScaleRatio
|
||||
Layout.preferredWidth: 45 * Style.uiScaleRatio
|
||||
horizontalAlignment: Text.AlignRight
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import qs.Modules.SessionMenu
|
||||
// Bar & Bar Components
|
||||
import qs.Modules.Bar
|
||||
import qs.Modules.Bar.Extras
|
||||
import qs.Modules.Bar.Audio
|
||||
import qs.Modules.Bar.Bluetooth
|
||||
import qs.Modules.Bar.Battery
|
||||
import qs.Modules.Bar.Calendar
|
||||
@@ -168,6 +169,11 @@ ShellRoot {
|
||||
objectName: "bluetoothPanel"
|
||||
}
|
||||
|
||||
AudioPanel {
|
||||
id: audioPanel
|
||||
objectName: "audioPanel"
|
||||
}
|
||||
|
||||
WallpaperPanel {
|
||||
id: wallpaperPanel
|
||||
objectName: "wallpaperPanel"
|
||||
|
||||
Reference in New Issue
Block a user