punktdateien/kde/plasma/plasmoids/com.siezi.plasma.mpdWidget/contents/logic/VolumeState.qml
2024-02-14 21:14:16 +01:00

121 lines
4 KiB
QML

import QtQuick 2.15
/**
* Decouples our internal volume state from immediatly reflecting mpd's
*/
Item {
id: root
/**
* Our internal volume value that is considered as the "truth" by the UI
*/
property int volume: 50
function set(value) {
if (value < 0 || value > 100) {
throw new Error("Invalid argument: volume must be between 0 and 100, is " + value)
}
// If one volume slider changes every other will update and therefore
// try to set that same value again. We ignore that identical value.
if (volume === value) {
return
}
// Use int. Otherwise our value fights with mpd's if mpd sends back a
// value that doesn't fit the int step.
value = Math.round(value)
root.volume = value
// Don't trigger sending to mpd again if we just received the "mixer"
// event value from mpd for our own value change.
if (root.volume === mpdState.mpdVolume) {
return
}
volumeDebounceTimer.value = value
// Don't use restart(). We don't want to send just the end value,
// the user has to receive volume change feedback while adjusting.
volumeDebounceTimer.start()
// Use restart(), we only care about the final value here and don't
// want to receive data we send before, but is outdated now (slider
// janks back to an older value).
volumeReceiverTimer.restart()
}
/**
* Change volume by a volume step
*
* @param {int} Value to change the current volume on a 0 to 100 scale
*/
function change(value) {
value = root.volume + value
value = value < 0 ? 0 : value
value = value > 100 ? 100 : value
set(value)
}
/**
* Takes a raw wheel input value, transforms into a volume value and applies
*
* @param {int} Raw mouse wheel value
*/
function wheel(value) {
// Wheel value is 120 int per "click". So divide by 120 for volume +/- 1.
// Hope it works for your mouse, it does for mine. Good luck.
let valueChangePerClick = 120
let desiredVolumeChangePerClick = 2
let volumeChange = value / valueChangePerClick * desiredVolumeChangePerClick
change(volumeChange)
}
Connections {
target: mpdState
function onMpdVolumeChanged() {
// We want to react to volume changes not comming from us. But we
// have to respect the time window we set for our own debounce-send
// to pass - It could be us. So we have to wait for at least that
// amount of time. Since the timer takes care of the volume update
// if it was us we return early and let the timer handle it.
if (volumeReceiverTimer.running) {
return
}
// So it wasn't us, let's adjust our volume to the mpd state.
root.volume = mpdState.mpdVolume
}
}
/**
* Debounce updating our "truth" from mpd
*/
Timer {
id: volumeReceiverTimer
property int value
// We have to at least wait for the time of our own send debounce-send to
// pass, otherwise we "jank back" to a previous, outdated value mpdState
// just received. Also wait at least a second, otherwise mpd may still
// serve the old value.
interval: 2 * volumeDebounceTimer.interval < 2000 ? 2000 : 2 * volumeDebounceTimer.interval
onTriggered: {
if (root.volume !== mpdState.mpdVolume) {
root.volume = mpdState.mpdVolume
}
}
}
/**
* Debounce volume sending
*
* Don't send every volume event our input devices generate. Plasma and mpd
* (or someone along the way down) feel much to crash happy about that.
*/
Timer {
id: volumeDebounceTimer
property int value
interval: 100
onTriggered: {
mpdState.setVolume(value)
}
}
}