126 lines
4.2 KiB
QML
126 lines
4.2 KiB
QML
import Quickshell
|
|
import Quickshell.Wayland
|
|
import Quickshell.Services.Pipewire
|
|
import QtQuick
|
|
import QtQuick.Layouts
|
|
import "../../config"
|
|
|
|
PanelWindow {
|
|
id: osd
|
|
visible: false
|
|
color: "transparent"
|
|
exclusiveZone: 0
|
|
|
|
// Top-right, just under the bar — same fixed distance from the bar and
|
|
// screen edge (Appearance.spacing) that every popup opened from the bar
|
|
// itself uses, so this lines up with them instead of floating
|
|
// bottom-center.
|
|
anchors { top: true; right: true }
|
|
margins.top: Appearance.spacing
|
|
margins.right: Appearance.spacing
|
|
|
|
implicitWidth: 272
|
|
implicitHeight: 54
|
|
|
|
WlrLayershell.layer: WlrLayer.Overlay
|
|
WlrLayershell.namespace: "quickshell:osd"
|
|
|
|
readonly property var sink: Pipewire.defaultAudioSink
|
|
PwObjectTracker { objects: osd.sink ? [osd.sink] : [] }
|
|
|
|
Timer {
|
|
id: hideTimer
|
|
interval: 1400
|
|
onTriggered: osd.visible = false
|
|
}
|
|
|
|
Connections {
|
|
target: osd.sink ? osd.sink.audio : null
|
|
function onVolumeChanged() { osd.visible = true; hideTimer.restart() }
|
|
function onMutedChanged() { osd.visible = true; hideTimer.restart() }
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
radius: Appearance.radius
|
|
color: Colors.surface
|
|
border.color: Colors.muted
|
|
border.width: 1
|
|
|
|
RowLayout {
|
|
anchors.fill: parent
|
|
anchors.margins: 14
|
|
spacing: 12
|
|
|
|
Text {
|
|
font.family: Appearance.iconFontFamily
|
|
font.variableAxes: Appearance.iconFontAxes
|
|
font.pixelSize: 18
|
|
color: Colors.foreground
|
|
text: {
|
|
const audio = osd.sink ? osd.sink.audio : null
|
|
if (!audio || audio.muted || audio.volume <= 0) return "volume_off"
|
|
return audio.volume < 0.5 ? "volume_down" : "volume_up"
|
|
}
|
|
}
|
|
|
|
Item {
|
|
id: slider
|
|
Layout.fillWidth: true
|
|
implicitHeight: 16
|
|
|
|
readonly property real value: osd.sink && osd.sink.audio ? osd.sink.audio.volume : 0
|
|
readonly property bool enabled: !!osd.sink
|
|
|
|
Rectangle {
|
|
id: track
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
width: parent.width
|
|
height: 4
|
|
radius: 0
|
|
color: Colors.muted
|
|
opacity: 0.35
|
|
}
|
|
Rectangle {
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
anchors.left: parent.left
|
|
width: track.width * Math.max(0, Math.min(1, slider.value))
|
|
height: 4
|
|
radius: 0
|
|
color: Colors.primary
|
|
opacity: slider.enabled ? 1 : 0.4
|
|
}
|
|
Rectangle {
|
|
width: 12
|
|
height: 12
|
|
radius: 6
|
|
color: Colors.primary
|
|
opacity: slider.enabled ? 1 : 0.4
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
x: Math.max(0, Math.min(track.width - width, track.width * slider.value - width / 2))
|
|
}
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
enabled: slider.enabled
|
|
cursorShape: Qt.PointingHandCursor
|
|
function updateFromX(mx) {
|
|
if (!osd.sink || !osd.sink.audio) return
|
|
osd.sink.audio.volume = Math.max(0, Math.min(1, mx / track.width))
|
|
}
|
|
onPressed: (mouse) => updateFromX(mouse.x)
|
|
onPositionChanged: (mouse) => { if (pressed) updateFromX(mouse.x) }
|
|
}
|
|
}
|
|
|
|
Text {
|
|
Layout.preferredWidth: 32
|
|
horizontalAlignment: Text.AlignRight
|
|
font.family: Appearance.fontFamily
|
|
font.pixelSize: Appearance.fontSizeSmall
|
|
color: Colors.muted
|
|
text: Math.round(slider.value * 100) + "%"
|
|
}
|
|
}
|
|
}
|
|
}
|