azalea_shell/window/taskbar/widget/notification/
mod.rs

1use azalea_service::{LocalListenerHandle, StaticServiceManager};
2use gtk::prelude::*;
3use relm4::{ComponentParts, ComponentSender, SimpleComponent, component, factory::FactoryHashMap};
4
5use crate::{
6    factory, icon,
7    service::{self, dbus::notification},
8};
9
10crate::init! {
11    Model {
12        num_unread: usize,
13        latest_notification: Option<notification::service::Notification>,
14        notifications: FactoryHashMap<u32, factory::notification::Model>,
15        _service_handle: LocalListenerHandle,
16    }
17
18    Config {
19    }
20}
21
22#[derive(Debug)]
23pub enum Input {
24    ClearLatest,
25    Close(u32),
26    Notifications(notification::Output),
27}
28
29#[component(pub)]
30impl SimpleComponent for Model {
31    type Init = Init;
32    type Input = Input;
33    type Output = ();
34
35    view! {
36        gtk::MenuButton {
37            #[wrap(Some)]
38            set_child= &gtk::Box {
39                set_spacing: 8,
40                set_hexpand: true,
41
42                gtk::Revealer {
43                    set_transition_type: gtk::RevealerTransitionType::SlideLeft,
44                    set_transition_duration: 1000,
45
46                    #[watch]
47                    set_reveal_child: model.latest_notification.is_some(),
48
49                    gtk::Label {
50                        set_max_width_chars: 60,
51                        set_ellipsize: gtk::pango::EllipsizeMode::End,
52
53                        #[watch]
54                        set_label: if model.latest_notification.is_some() {
55                            &model.latest_notification.as_ref().unwrap().summary
56                        } else { "" }
57                    },
58                },
59
60                gtk::Image {
61                    set_icon_name: Some(icon::BELL),
62                },
63
64                gtk::Label {
65                    #[watch]
66                    set_visible: model.num_unread > 0,
67
68                    #[watch]
69                    set_label: &format!("{}", model.num_unread),
70                },
71            },
72
73            #[wrap(Some)]
74            set_popover = &gtk::Popover {
75                set_position: gtk::PositionType::Right,
76
77                connect_notify: (Some("visible"), move |this, _| {
78                    if !this.get_visible() {
79                        drop(sender.input_sender().send(Input::ClearLatest));
80                    }
81                }),
82
83                gtk::ScrolledWindow {
84                    set_propagate_natural_width: true,
85                    set_propagate_natural_height: true,
86
87                    #[local_ref]
88                    notifications_widget -> gtk::Box {
89                        add_css_class: "azalea-transparent",
90                        set_orientation: gtk::Orientation::Vertical,
91                        set_spacing: 5,
92                    }
93                }
94            },
95        },
96    }
97
98    fn init(
99        _init: Self::Init,
100        _root: Self::Root,
101        sender: ComponentSender<Self>,
102    ) -> ComponentParts<Self> {
103        let model = Model {
104            latest_notification: None,
105            num_unread: 0,
106            _service_handle: service::dbus::notification::Service::forward_local(
107                sender.input_sender().clone(),
108                Input::Notifications,
109            ),
110            notifications: FactoryHashMap::builder()
111                .launch(gtk::Box::default())
112                .forward(sender.input_sender(), |output| match output {
113                    factory::notification::Output::Close(id) => Input::Close(id),
114                }),
115        };
116
117        let notifications_widget = model.notifications.widget();
118        let widgets = view_output!();
119
120        ComponentParts { model, widgets }
121    }
122
123    fn update(&mut self, message: Self::Input, _sender: ComponentSender<Self>) {
124        match message {
125            Input::ClearLatest => {
126                self.latest_notification = None;
127            }
128            Input::Close(id) => {
129                self.notifications.remove(&id);
130                self.num_unread -= 1;
131                // TODO: Call Close method in notification service
132            }
133            Input::Notifications(message) => match message {
134                notification::Output::Notification(notification) => {
135                    self.num_unread += 1;
136                    self.latest_notification = Some(notification.clone());
137                    self.notifications.insert(notification.id, notification);
138                }
139            },
140        }
141    }
142}