duat_base/widgets/
notifications.rs1use std::sync::{
11 Once,
12 atomic::{AtomicBool, Ordering},
13};
14
15use duat_core::{
16 context::{self, Handle, Level, Record},
17 data::Pass,
18 hook::{self, KeysSent},
19 text::{Text, txt},
20 ui::{PushSpecs, PushTarget, Side, Widget},
21};
22
23pub struct Notifications {
56 logs: context::Logs,
57 text: Text,
58 format_rec: Box<dyn FnMut(Record) -> Text + Send>,
59 levels: Vec<Level>,
60 last_rec: Option<usize>,
61 get_mask: Box<dyn FnMut(Record) -> &'static str + Send>,
62 request_width: bool,
63}
64
65static CLEAR_NOTIFS: AtomicBool = AtomicBool::new(false);
66
67impl Notifications {
68 pub fn builder() -> NotificationsOpts {
71 static ONCE: Once = Once::new();
72 ONCE.call_once(|| {
73 hook::add::<KeysSent>(|_, _| {
74 CLEAR_NOTIFS.store(true, Ordering::Relaxed);
75 Ok(())
76 });
77 });
78 NotificationsOpts::default()
79 }
80}
81
82impl Widget for Notifications {
83 fn update(pa: &mut Pass, handle: &Handle<Self>) {
84 let clear_notifs = CLEAR_NOTIFS.swap(false, Ordering::Relaxed);
85 let notifs = handle.write(pa);
86
87 if notifs.logs.has_changed()
88 && let Some((i, rec)) = notifs.logs.last_with_levels(¬ifs.levels)
89 && notifs.last_rec.is_none_or(|last_i| last_i < i)
90 {
91 handle.set_mask((notifs.get_mask)(rec.clone()));
92 notifs.text = (notifs.format_rec)(rec);
93 notifs.last_rec = Some(i);
94
95 if notifs.request_width {
96 let notifs = handle.read(pa);
97 let width = handle
98 .area()
99 .width_of_text(pa, notifs.get_print_opts(), ¬ifs.text)
100 .unwrap();
101 handle.area().set_width(pa, width).unwrap();
102 }
103 } else if clear_notifs {
104 handle.set_mask("");
105 if notifs.text != Text::new() {
106 notifs.text = Text::new();
107
108 if notifs.request_width {
109 let notifs = handle.read(pa);
110 let width = handle
111 .area()
112 .width_of_text(pa, notifs.get_print_opts(), ¬ifs.text)
113 .unwrap();
114 handle.area().set_width(pa, width).unwrap();
115 }
116 }
117 }
118 }
119
120 fn text(&self) -> &Text {
121 &self.text
122 }
123
124 fn text_mut(&mut self) -> &mut Text {
125 &mut self.text
126 }
127
128 fn needs_update(&self, _: &Pass) -> bool {
129 self.logs.has_changed() || CLEAR_NOTIFS.load(Ordering::Relaxed)
130 }
131}
132
133#[doc(hidden)]
145pub struct NotificationsOpts {
146 fmt: Box<dyn FnMut(Record) -> Text + Send>,
147 get_mask: Box<dyn FnMut(Record) -> &'static str + Send>,
148 allowed_levels: Vec<Level>,
149 request_width: bool,
150}
151
152impl NotificationsOpts {
153 pub fn push_on(self, pa: &mut Pass, push_target: &impl PushTarget) -> Handle<Notifications> {
155 let notifications = Notifications {
156 logs: context::logs(),
157 text: Text::new(),
158 format_rec: self.fmt,
159 get_mask: self.get_mask,
160 levels: self.allowed_levels,
161 last_rec: None,
162 request_width: self.request_width,
163 };
164 let specs = PushSpecs {
165 side: Side::Below,
166 height: Some(1.0),
167 ..Default::default()
168 };
169
170 push_target.push_inner(pa, notifications, specs)
171 }
172
173 pub fn fmt<T: Into<Text>>(&mut self, mut fmt: impl FnMut(Record) -> T + Send + 'static) {
181 self.fmt = Box::new(move |rec| fmt(rec).into());
182 }
183
184 pub fn set_allowed_levels(&mut self, levels: impl IntoIterator<Item = Level>) {
189 self.allowed_levels = levels.into_iter().collect();
190 }
191
192 pub fn set_mask(&mut self, get_mask: impl FnMut(Record) -> &'static str + Send + 'static) {
196 self.get_mask = Box::new(get_mask);
197 }
198
199 pub(crate) fn request_width(&mut self) {
201 self.request_width = true;
202 }
203}
204
205impl Default for NotificationsOpts {
206 fn default() -> Self {
207 fn default_fmt(rec: Record) -> Text {
208 match rec.level() {
209 Level::Error | Level::Warn | Level::Debug => {
210 txt!(
211 "[buffer]{}[notifs.colon]:[] {}",
212 rec.location(),
213 rec.text().clone()
214 )
215 }
216 Level::Info => rec.text().clone(),
217 Level::Trace => unreachable!(),
218 }
219 }
220 fn default_get_mask(rec: Record) -> &'static str {
221 match rec.level() {
222 context::Level::Error => "error",
223 context::Level::Warn => "warn",
224 context::Level::Info => "info",
225 context::Level::Debug => "debug",
226 context::Level::Trace => unreachable!(),
227 }
228 }
229
230 Self {
231 fmt: Box::new(default_fmt),
232 get_mask: Box::new(default_get_mask),
233 allowed_levels: vec![Level::Error, Level::Warn, Level::Info],
234 request_width: false,
235 }
236 }
237}