121 lines
4 KiB
QML
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)
|
|
}
|
|
}
|
|
}
|