cosmic-applets/applets/cosmic-applet-audio/src/volume_scale.rs
2022-07-22 13:26:36 -07:00

115 lines
3.5 KiB
Rust

use gtk4::{glib, prelude::*, subclass::prelude::*};
use libpulse_binding::volume::{ChannelVolumes, Volume};
use std::{
cell::{Cell, RefCell},
future::Future,
pin::Pin,
rc::Rc,
};
use crate::PA;
// Component
#[derive(Default)]
pub struct VolumeScaleImp {
name: Rc<RefCell<Option<String>>>,
}
#[glib::object_subclass]
impl ObjectSubclass for VolumeScaleImp {
const NAME: &'static str = "VolumeScale";
type Type = VolumeScale;
type ParentType = gtk4::Scale;
}
impl ObjectImpl for VolumeScaleImp {}
impl WidgetImpl for VolumeScaleImp {}
impl RangeImpl for VolumeScaleImp {}
impl ScaleImpl for VolumeScaleImp {}
glib::wrapper! {
pub struct VolumeScale(ObjectSubclass<VolumeScaleImp>)
@extends gtk4::Scale, gtk4::Range, gtk4::Widget,
@implements gtk4::Accessible, gtk4::Orientable;
}
impl VolumeScale {
pub fn new(pa: PA, sink: bool) -> Self {
let scale: VolumeScale = glib::Object::new(&[]).unwrap();
scale.set_range(0., 100.);
let name = scale.imp().name.clone();
let updater = Updater::new(move |value: f64| {
let name = name.clone();
let pa = pa.clone();
async move {
let mut volumes = ChannelVolumes::default();
let volume = value * (Volume::NORMAL.0 as f64) / 100.;
volumes.set(1, Volume(volume as _)); // XXX ?
let name_ref = name.borrow();
if let Some(name) = name_ref.as_deref() {
if sink {
let fut = pa.set_sink_volume_by_name(name, &volumes);
drop(name_ref);
fut.await;
} else {
let fut = pa.set_source_volume_by_name(name, &volumes);
drop(name_ref);
fut.await;
}
}
}
});
scale.connect_change_value(move |_scale, _scroll, value| {
updater.update(value);
gtk4::Inhibit(false)
});
scale
}
pub fn set_volume(&self, volume: &ChannelVolumes) {
let value = volume.avg().0 as f64 / (Volume::NORMAL.0 as f64) * 100.;
self.set_value(value);
}
pub fn set_name(&self, name: Option<String>) {
*self.imp().name.borrow_mut() = name;
}
}
// Perform an asynchronous update operation without queuing more than one set.
struct Updater<T: 'static> {
updating: Rc<Cell<bool>>,
value: Rc<Cell<Option<T>>>,
update_fn: Rc<dyn Fn(T) -> Pin<Box<dyn Future<Output = ()> + 'static>>>,
}
impl<T: 'static> Updater<T> {
fn new<Fut: Future<Output = ()> + 'static, F: Fn(T) -> Fut + 'static>(f: F) -> Self {
let value = Rc::new(Cell::new(None));
let updating = Rc::new(Cell::new(false));
let update_fn =
Rc::new(move |value| Box::pin(f(value)) as Pin<Box<dyn Future<Output = ()>>>);
Self {
updating,
value,
update_fn,
}
}
fn update(&self, value: T) {
self.value.set(Some(value));
if self.updating.replace(true) == false {
let value = self.value.clone();
let updating = self.updating.clone();
let update_fn = self.update_fn.clone();
glib::MainContext::default().spawn_local(async move {
while let Some(value) = value.take() {
update_fn(value).await;
}
updating.set(false);
});
}
}
}