duat_base/widgets/
notifications.rs1use std::sync::{
11 Mutex, Once,
12 atomic::{AtomicBool, Ordering},
13};
14
15use duat_core::{
16 context::{self, Handle, Level, Record},
17 data::Pass,
18 hook::{self, KeySent},
19 text::{Text, TextMut},
20 ui::{PushSpecs, PushTarget, Side, Widget},
21};
22
23pub struct Notifications {
56 logs: context::Logs,
57 text: Text,
58 fmt: Option<Box<dyn FnMut(Record) -> Text + Send>>,
59 levels: Vec<Level>,
60 last_rec: Option<usize>,
61 get_mask: Option<Box<dyn FnMut(Record) -> &'static str + Send>>,
62 request_width: bool,
63}
64
65static CLEAR_NOTIFS: AtomicBool = AtomicBool::new(false);
66#[allow(clippy::type_complexity)]
67static GLOBAL_FMT: Mutex<Option<Box<dyn FnMut(Record) -> Text + Send>>> = Mutex::new(None);
68#[allow(clippy::type_complexity)]
69static GLOBAL_GET_MASK: Mutex<Option<Box<dyn FnMut(Record) -> &'static str + Send>>> =
70 Mutex::new(None);
71
72impl Notifications {
73 pub fn builder() -> NotificationsOpts {
76 static ONCE: Once = Once::new();
77 ONCE.call_once(|| {
78 hook::add::<KeySent>(|_, _| CLEAR_NOTIFS.store(true, Ordering::Relaxed));
79 });
80 NotificationsOpts::default()
81 }
82}
83
84impl Widget for Notifications {
85 fn update(pa: &mut Pass, handle: &Handle<Self>) {
86 let clear_notifs = CLEAR_NOTIFS.swap(false, Ordering::Relaxed);
87 let notifs = handle.write(pa);
88
89 if notifs.logs.has_changed()
90 && let Some((i, rec)) = notifs.logs.last_with_levels(¬ifs.levels)
91 && notifs.last_rec.is_none_or(|last_i| last_i < i)
92 {
93 let mut global_fmt = GLOBAL_FMT.lock().unwrap();
94 let mut global_get_mask = GLOBAL_GET_MASK.lock().unwrap();
95
96 handle.set_mask(if let Some(get_mask) = notifs.get_mask.as_mut() {
97 get_mask(rec.clone())
98 } else if let Some(get_mask) = global_get_mask.as_mut() {
99 get_mask(rec.clone())
100 } else {
101 default_get_mask(rec.clone())
102 });
103
104 notifs.text = if let Some(fmt) = notifs.fmt.as_mut() {
105 fmt(rec)
106 } else if let Some(fmt) = global_fmt.as_mut() {
107 fmt(rec)
108 } else {
109 default_fmt(rec)
110 };
111 notifs.last_rec = Some(i);
112
113 if notifs.request_width {
114 let notifs = handle.read(pa);
115 let size = handle
116 .area()
117 .size_of_text(pa, notifs.get_print_opts(), ¬ifs.text)
118 .unwrap();
119 handle.area().set_width(pa, size.x).unwrap();
120 handle.area().set_height(pa, size.y).unwrap();
121 }
122 } else if clear_notifs {
123 handle.set_mask("");
124 if notifs.text != Text::new() {
125 notifs.text = Text::new();
126
127 if notifs.request_width {
128 let notifs = handle.read(pa);
129 let size = handle
130 .area()
131 .size_of_text(pa, notifs.get_print_opts(), ¬ifs.text)
132 .unwrap();
133 handle.area().set_width(pa, size.x).unwrap();
134 handle.area().set_height(pa, size.y).unwrap();
135 }
136 }
137 }
138 }
139
140 fn text(&self) -> &Text {
141 &self.text
142 }
143
144 fn text_mut(&mut self) -> TextMut<'_> {
145 self.text.as_mut()
146 }
147
148 fn needs_update(&self, _: &Pass) -> bool {
149 self.logs.has_changed() || CLEAR_NOTIFS.load(Ordering::Relaxed)
150 }
151}
152
153#[doc(hidden)]
165#[derive(Clone)]
166pub struct NotificationsOpts {
167 allowed_levels: Vec<Level>,
168 request_width: bool,
169}
170
171impl NotificationsOpts {
172 pub fn push_on(self, pa: &mut Pass, push_target: &impl PushTarget) -> Handle<Notifications> {
174 let notifications = Notifications {
175 logs: context::logs(),
176 text: Text::new(),
177 fmt: None,
178 get_mask: None,
179 levels: self.allowed_levels,
180 last_rec: None,
181 request_width: self.request_width,
182 };
183 let specs = PushSpecs {
184 side: Side::Below,
185 height: Some(1.0),
186 ..Default::default()
187 };
188
189 push_target.push_inner(pa, notifications, specs)
190 }
191
192 pub fn fmt(&mut self, fmt: impl FnMut(Record) -> Text + Send + 'static) {
200 *GLOBAL_FMT.lock().unwrap() = Some(Box::new(fmt));
201 }
202
203 pub fn set_mask(&mut self, get_mask: impl FnMut(Record) -> &'static str + Send + 'static) {
207 *GLOBAL_GET_MASK.lock().unwrap() = Some(Box::new(get_mask));
208 }
209
210 pub fn set_allowed_levels(&mut self, levels: impl IntoIterator<Item = Level>) {
215 self.allowed_levels = levels.into_iter().collect();
216 }
217
218 pub(crate) fn request_width(&mut self) {
220 self.request_width = true;
221 }
222}
223
224impl Default for NotificationsOpts {
225 fn default() -> Self {
226 Self {
227 allowed_levels: vec![Level::Error, Level::Warn, Level::Info],
228 request_width: false,
229 }
230 }
231}
232
233fn default_fmt(rec: Record) -> Text {
234 match rec.level() {
235 Level::Error | Level::Warn | Level::Debug => rec.text().clone(),
236 Level::Info => rec.text().clone(),
237 Level::Trace => unreachable!(),
238 }
239}
240fn default_get_mask(rec: Record) -> &'static str {
241 match rec.level() {
242 context::Level::Error => "error",
243 context::Level::Warn => "warn",
244 context::Level::Info => "info",
245 context::Level::Debug => "debug",
246 context::Level::Trace => unreachable!(),
247 }
248}