feat: audio osd
This commit is contained in:
parent
83020b8b1f
commit
fb471f1023
13 changed files with 427 additions and 7 deletions
14
config/OsdConfig.qml
Normal file
14
config/OsdConfig.qml
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
pragma Singleton
|
||||||
|
|
||||||
|
import Quickshell
|
||||||
|
import QtQuick
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
readonly property int hideDelay: 1500
|
||||||
|
readonly property Sizes sizes: Sizes {}
|
||||||
|
|
||||||
|
component Sizes: QtObject {
|
||||||
|
readonly property int sliderWidth: 30
|
||||||
|
readonly property int sliderHeight: 150
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,8 +11,6 @@ Shape {
|
||||||
readonly property int roundingY: Math.min(rounding, realWrapperHeight / 2)
|
readonly property int roundingY: Math.min(rounding, realWrapperHeight / 2)
|
||||||
readonly property real wrapperHeight: realWrapperHeight - 1 // Pixel issues :sob:
|
readonly property real wrapperHeight: realWrapperHeight - 1 // Pixel issues :sob:
|
||||||
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
|
|
||||||
preferredRendererType: Shape.CurveRenderer
|
preferredRendererType: Shape.CurveRenderer
|
||||||
opacity: Appearance.transparency.enabled ? Appearance.transparency.base : 1
|
opacity: Appearance.transparency.enabled ? Appearance.transparency.base : 1
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,6 @@ Item {
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
anchors.bottom: search.top
|
anchors.bottom: search.top
|
||||||
anchors.bottomMargin: root.spacing
|
anchors.bottomMargin: root.spacing
|
||||||
anchors.margins: root.padding
|
|
||||||
|
|
||||||
ContentList {
|
ContentList {
|
||||||
id: list
|
id: list
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ Scope {
|
||||||
id: bg
|
id: bg
|
||||||
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
|
||||||
wrapperWidth: wrapper.width
|
wrapperWidth: wrapper.width
|
||||||
realWrapperHeight: Math.min(wrapper.height, content.height)
|
realWrapperHeight: Math.min(wrapper.height, content.height)
|
||||||
|
|
@ -43,6 +44,7 @@ Scope {
|
||||||
id: wrapper
|
id: wrapper
|
||||||
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
|
||||||
implicitWidth: content.width + bg.rounding * 2
|
implicitWidth: content.width + bg.rounding * 2
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,6 @@ Item {
|
||||||
required property real contentHeight
|
required property real contentHeight
|
||||||
property bool shouldBeVisible
|
property bool shouldBeVisible
|
||||||
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
|
|
||||||
height: 0
|
height: 0
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
|
|
|
||||||
61
modules/osd/Background.qml
Normal file
61
modules/osd/Background.qml
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
import "root:/config"
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Shapes
|
||||||
|
|
||||||
|
Shape {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property real realWrapperWidth
|
||||||
|
required property real wrapperHeight
|
||||||
|
readonly property int rounding: Appearance.rounding.large
|
||||||
|
readonly property int roundingX: Math.min(rounding, realWrapperWidth / 2)
|
||||||
|
readonly property real wrapperWidth: realWrapperWidth - 1 // Pixel issues :sob:
|
||||||
|
|
||||||
|
preferredRendererType: Shape.CurveRenderer
|
||||||
|
opacity: Appearance.transparency.enabled ? Appearance.transparency.base : 1
|
||||||
|
|
||||||
|
ShapePath {
|
||||||
|
strokeWidth: -1
|
||||||
|
fillColor: Appearance.colours.m3surfaceContainer
|
||||||
|
|
||||||
|
startX: root.wrapperWidth
|
||||||
|
|
||||||
|
PathArc {
|
||||||
|
relativeX: -root.roundingX
|
||||||
|
relativeY: root.rounding
|
||||||
|
radiusX: root.roundingX
|
||||||
|
radiusY: root.rounding
|
||||||
|
}
|
||||||
|
PathLine {
|
||||||
|
x: root.roundingX
|
||||||
|
relativeY: 0
|
||||||
|
}
|
||||||
|
PathArc {
|
||||||
|
relativeX: -root.roundingX
|
||||||
|
relativeY: root.rounding
|
||||||
|
radiusX: root.roundingX
|
||||||
|
radiusY: root.rounding
|
||||||
|
direction: PathArc.Counterclockwise
|
||||||
|
}
|
||||||
|
PathLine {
|
||||||
|
y: root.wrapperHeight - root.rounding * 2
|
||||||
|
}
|
||||||
|
PathArc {
|
||||||
|
relativeX: root.roundingX
|
||||||
|
relativeY: root.rounding
|
||||||
|
radiusX: root.roundingX
|
||||||
|
radiusY: root.rounding
|
||||||
|
direction: PathArc.Counterclockwise
|
||||||
|
}
|
||||||
|
PathLine {
|
||||||
|
x: root.wrapperWidth - root.roundingX
|
||||||
|
relativeY: 0
|
||||||
|
}
|
||||||
|
PathArc {
|
||||||
|
relativeX: root.roundingX
|
||||||
|
relativeY: root.rounding
|
||||||
|
radiusX: root.roundingX
|
||||||
|
radiusY: root.rounding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
42
modules/osd/Content.qml
Normal file
42
modules/osd/Content.qml
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
import "root:/widgets"
|
||||||
|
import "root:/services"
|
||||||
|
import "root:/config"
|
||||||
|
import Quickshell
|
||||||
|
import QtQuick
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property ShellScreen screen
|
||||||
|
|
||||||
|
padding: Appearance.padding.large
|
||||||
|
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.right: parent.right
|
||||||
|
|
||||||
|
spacing: Appearance.spacing.normal
|
||||||
|
|
||||||
|
VerticalSlider {
|
||||||
|
icon: {
|
||||||
|
if (Audio.muted)
|
||||||
|
return "no_sound";
|
||||||
|
if (value >= 0.5)
|
||||||
|
return "volume_up";
|
||||||
|
if (value > 0)
|
||||||
|
return "volume_down";
|
||||||
|
return "volume_mute";
|
||||||
|
}
|
||||||
|
value: Audio.volume
|
||||||
|
onMoved: Audio.setVolume(value)
|
||||||
|
|
||||||
|
implicitWidth: OsdConfig.sizes.sliderWidth
|
||||||
|
implicitHeight: OsdConfig.sizes.sliderHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
VerticalSlider {
|
||||||
|
icon: "brightness_6"
|
||||||
|
|
||||||
|
implicitWidth: OsdConfig.sizes.sliderWidth
|
||||||
|
implicitHeight: OsdConfig.sizes.sliderHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
89
modules/osd/Osd.qml
Normal file
89
modules/osd/Osd.qml
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
import "root:/widgets"
|
||||||
|
import "root:/services"
|
||||||
|
import "root:/config"
|
||||||
|
import Quickshell
|
||||||
|
import QtQuick
|
||||||
|
|
||||||
|
Scope {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property bool osdVisible
|
||||||
|
|
||||||
|
function show(): void {
|
||||||
|
root.osdVisible = true;
|
||||||
|
timer.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: Audio
|
||||||
|
|
||||||
|
function onMutedChanged(): void {
|
||||||
|
root.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onVolumeChanged(): void {
|
||||||
|
root.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: timer
|
||||||
|
|
||||||
|
interval: OsdConfig.hideDelay
|
||||||
|
onTriggered: root.osdVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
Variants {
|
||||||
|
model: Quickshell.screens
|
||||||
|
|
||||||
|
LazyLoader {
|
||||||
|
loading: true
|
||||||
|
|
||||||
|
required property ShellScreen modelData
|
||||||
|
|
||||||
|
StyledWindow {
|
||||||
|
id: win
|
||||||
|
|
||||||
|
screen: parent.modelData
|
||||||
|
name: "osd"
|
||||||
|
visible: wrapper.shouldBeVisible
|
||||||
|
|
||||||
|
mask: Region {
|
||||||
|
item: wrapper
|
||||||
|
}
|
||||||
|
|
||||||
|
anchors.left: true
|
||||||
|
anchors.right: true
|
||||||
|
height: wrapper.height
|
||||||
|
|
||||||
|
Background {
|
||||||
|
id: bg
|
||||||
|
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.right: parent.right
|
||||||
|
|
||||||
|
wrapperHeight: wrapper.height
|
||||||
|
realWrapperWidth: Math.min(wrapper.width, content.width)
|
||||||
|
}
|
||||||
|
|
||||||
|
Wrapper {
|
||||||
|
id: wrapper
|
||||||
|
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.right: parent.right
|
||||||
|
|
||||||
|
implicitHeight: content.height + bg.rounding * 2
|
||||||
|
|
||||||
|
osdVisible: root.osdVisible
|
||||||
|
contentWidth: content.width
|
||||||
|
|
||||||
|
Content {
|
||||||
|
id: content
|
||||||
|
|
||||||
|
screen: parent.modelData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
62
modules/osd/Wrapper.qml
Normal file
62
modules/osd/Wrapper.qml
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
import "root:/config"
|
||||||
|
import QtQuick
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property bool osdVisible
|
||||||
|
required property real contentWidth
|
||||||
|
property bool shouldBeVisible
|
||||||
|
|
||||||
|
width: 0
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
states: State {
|
||||||
|
name: "visible"
|
||||||
|
when: root.osdVisible
|
||||||
|
|
||||||
|
PropertyChanges {
|
||||||
|
root.width: contentWidth
|
||||||
|
root.shouldBeVisible: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transitions: [
|
||||||
|
Transition {
|
||||||
|
from: ""
|
||||||
|
to: "visible"
|
||||||
|
|
||||||
|
SequentialAnimation {
|
||||||
|
PropertyAction {
|
||||||
|
target: root
|
||||||
|
property: "shouldBeVisible"
|
||||||
|
}
|
||||||
|
NumberAnimation {
|
||||||
|
target: root
|
||||||
|
property: "width"
|
||||||
|
duration: Appearance.anim.durations.large
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: Appearance.anim.curves.emphasizedDecel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Transition {
|
||||||
|
from: "visible"
|
||||||
|
to: ""
|
||||||
|
|
||||||
|
SequentialAnimation {
|
||||||
|
NumberAnimation {
|
||||||
|
target: root
|
||||||
|
property: "width"
|
||||||
|
duration: Appearance.anim.durations.normal
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: Appearance.anim.curves.emphasizedAccel
|
||||||
|
}
|
||||||
|
PropertyAction {
|
||||||
|
target: root
|
||||||
|
property: "shouldBeVisible"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
pragma Singleton
|
pragma Singleton
|
||||||
|
|
||||||
import "root:/utils/scripts/fuzzysort.js" as Fuzzy
|
import "root:/utils/scripts/fuzzysort.js" as Fuzzy
|
||||||
import "root:/widgets"
|
|
||||||
import "root:/config"
|
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
|
|
||||||
|
|
|
||||||
25
services/Audio.qml
Normal file
25
services/Audio.qml
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
pragma Singleton
|
||||||
|
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Services.Pipewire
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
readonly property PwNode sink: Pipewire.defaultAudioSink
|
||||||
|
readonly property PwNode source: Pipewire.defaultAudioSource
|
||||||
|
|
||||||
|
readonly property bool muted: sink?.audio?.muted ?? false
|
||||||
|
readonly property real volume: sink?.audio?.volume ?? 0
|
||||||
|
|
||||||
|
function setVolume(volume: real): void {
|
||||||
|
if (sink?.ready && sink?.audio) {
|
||||||
|
sink.audio.muted = false;
|
||||||
|
sink.audio.volume = volume;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PwObjectTracker {
|
||||||
|
objects: [Pipewire.defaultAudioSink, Pipewire.defaultAudioSource]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
import "modules/bar"
|
import "modules/bar"
|
||||||
import "modules/launcher"
|
import "modules/launcher"
|
||||||
|
import "modules/osd"
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
|
||||||
ShellRoot {
|
ShellRoot {
|
||||||
Bar {}
|
Bar {}
|
||||||
Launcher {}
|
Launcher {}
|
||||||
|
Osd {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
130
widgets/VerticalSlider.qml
Normal file
130
widgets/VerticalSlider.qml
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
import "root:/widgets"
|
||||||
|
import "root:/config"
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Effects
|
||||||
|
|
||||||
|
Slider {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property string icon
|
||||||
|
|
||||||
|
orientation: Qt.Vertical
|
||||||
|
|
||||||
|
background: StyledRect {
|
||||||
|
color: Appearance.alpha(Appearance.colours.m3surfaceContainerHigh, true)
|
||||||
|
radius: Appearance.rounding.full
|
||||||
|
|
||||||
|
StyledRect {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
|
||||||
|
y: root.handle.y
|
||||||
|
implicitHeight: parent.height - y
|
||||||
|
|
||||||
|
color: Appearance.alpha(Appearance.colours.m3secondary, true)
|
||||||
|
radius: Appearance.rounding.full
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handle: Item {
|
||||||
|
id: handle
|
||||||
|
|
||||||
|
property bool moving
|
||||||
|
|
||||||
|
y: root.visualPosition * (root.availableHeight - height)
|
||||||
|
implicitWidth: root.width
|
||||||
|
implicitHeight: root.width
|
||||||
|
|
||||||
|
RectangularShadow {
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: rect.radius
|
||||||
|
color: Appearance.colours.m3shadow
|
||||||
|
blur: 5
|
||||||
|
spread: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledRect {
|
||||||
|
id: rect
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
color: Appearance.alpha(Appearance.colours.m3inverseSurface, true)
|
||||||
|
radius: Appearance.rounding.full
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
id: icon
|
||||||
|
|
||||||
|
animate: true
|
||||||
|
text: root.icon
|
||||||
|
color: Appearance.colours.m3onPrimary
|
||||||
|
anchors.centerIn: parent
|
||||||
|
|
||||||
|
states: State {
|
||||||
|
name: "value"
|
||||||
|
when: handle.moving
|
||||||
|
|
||||||
|
PropertyChanges {
|
||||||
|
icon.animate: false
|
||||||
|
icon.text: Math.round(root.value * 100)
|
||||||
|
icon.font.pointSize: Appearance.font.size.small
|
||||||
|
icon.font.family: Appearance.font.family.sans
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transitions: Transition {
|
||||||
|
from: "*"
|
||||||
|
to: "*"
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
target: icon
|
||||||
|
property: "scale"
|
||||||
|
from: 1
|
||||||
|
to: 0
|
||||||
|
duration: Appearance.anim.durations.normal
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: Appearance.anim.curves.standardAccel
|
||||||
|
}
|
||||||
|
PropertyAction {
|
||||||
|
target: icon
|
||||||
|
properties: "animate,text,font.pointSize,font.family"
|
||||||
|
}
|
||||||
|
NumberAnimation {
|
||||||
|
target: icon
|
||||||
|
property: "scale"
|
||||||
|
from: 0
|
||||||
|
to: 1
|
||||||
|
duration: Appearance.anim.durations.normal
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: Appearance.anim.curves.standardDecel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onPressedChanged: handle.moving = pressed
|
||||||
|
|
||||||
|
onValueChanged: {
|
||||||
|
handle.moving = true;
|
||||||
|
stateChangeDelay.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: stateChangeDelay
|
||||||
|
|
||||||
|
interval: 500
|
||||||
|
onTriggered: {
|
||||||
|
if (!root.pressed)
|
||||||
|
handle.moving = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on value {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Appearance.anim.durations.large
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: Appearance.anim.curves.standard
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue