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>>, } #[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) @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) { *self.imp().name.borrow_mut() = name; } } // Perform an asynchronous update operation without queuing more than one set. struct Updater { updating: Rc>, value: Rc>>, update_fn: Rc Pin + 'static>>>, } impl Updater { fn new + '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>>); 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); }); } } }