azalea_shell/window/taskbar/widget/
audio.rs

1use azalea_service::{LocalListenerHandle, StaticServiceManager};
2use gtk::prelude::*;
3use relm4::RelmWidgetExt;
4use relm4::{ComponentParts, ComponentSender, SimpleComponent, component};
5
6use crate::icon;
7
8use crate::service;
9
10crate::init! {
11    Model {
12        system_volume: f64,
13        _service_handle: LocalListenerHandle,
14    }
15
16    Config {
17    }
18}
19
20#[derive(Debug)]
21pub enum Input {
22    Scroll(f64),
23    Audio(service::audio::Output),
24}
25
26#[component(pub)]
27impl SimpleComponent for Model {
28    type Init = Init;
29    type Input = Input;
30    type Output = ();
31
32    view! {
33        gtk::Box {
34            inline_css: "padding: 2px 8px 2px 4px;",
35            set_valign: gtk::Align::Center,
36            set_css_classes: &[
37                "azalea-secondary-border",
38                "azalea-circle-bubble",
39                "azalea-secondary-container",
40                "azalea-secondary-container-hover",
41            ],
42            set_spacing: 8,
43
44            gtk::Image {
45                set_icon_name: Some(icon::AUDIO),
46            },
47
48            gtk::Label {
49                #[watch]
50                set_label: &format!("{}%", model.system_volume_percent()),
51            },
52
53            gtk::Frame {
54                set_width_request: 100,
55                set_height_request: 5,
56                set_vexpand: false,
57                set_valign: gtk::Align::Center,
58
59                #[watch]
60                inline_css: &format!(
61                    "background-image: linear-gradient(to right, var(--secondary) {}%, var(--on-secondary) 0);",
62                    model.system_volume_percent()
63                ),
64            },
65
66            add_controller = gtk::EventControllerScroll {
67                set_flags: gtk::EventControllerScrollFlags::BOTH_AXES,
68                connect_scroll[sender] => move |_this, _dx, dy| {
69                    sender.input(Input::Scroll(dy));
70                    false.into()
71                },
72            },
73        },
74    }
75
76    fn init(
77        _init: Self::Init,
78        _root: Self::Root,
79        sender: ComponentSender<Self>,
80    ) -> ComponentParts<Self> {
81        let model = Model {
82            system_volume: 0.,
83            _service_handle: service::audio::Service::forward_local(
84                sender.input_sender().clone(),
85                Input::Audio,
86            ),
87        };
88
89        let widgets = view_output!();
90
91        ComponentParts { model, widgets }
92    }
93
94    fn update(&mut self, input: Self::Input, _sender: ComponentSender<Self>) {
95        match input {
96            Input::Scroll(delta) => {
97                let volume = self.system_volume - delta * 0.01;
98                if volume >= 0. && volume <= 1. {
99                    self.system_volume = volume;
100                    service::audio::Service::send(service::audio::Input::SystemVolume(
101                        self.system_volume,
102                    ));
103                }
104            }
105            Input::Audio(output) => match output {
106                service::audio::Output::SystemVolume(volume) => self.system_volume = volume,
107            },
108        }
109    }
110}
111
112impl Model {
113    fn system_volume_percent(&self) -> i64 {
114        (self.system_volume * 100.) as i64
115    }
116}