azalea_shell/window/taskbar/widget/notification/
mod.rs1use 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= >k::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 = >k::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 }
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}