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 { side: Side::Below, height: Some(1.0), .. };
165
166 push_target.push_inner(pa, notifications, specs)
167 }
168
169 pub fn fmt<T: Into<Text>>(&mut self, mut fmt: impl FnMut(Record) -> T + Send + 'static) {
177 self.fmt = Box::new(move |rec| fmt(rec).into());
178 }
179
180 pub fn set_allowed_levels(&mut self, levels: impl IntoIterator<Item = Level>) {
185 self.allowed_levels = levels.into_iter().collect();
186 }
187
188 pub fn set_mask(&mut self, get_mask: impl FnMut(Record) -> &'static str + Send + 'static) {
192 self.get_mask = Box::new(get_mask);
193 }
194
195 pub(crate) fn request_width(&mut self) {
197 self.request_width = true;
198 }
199}
200
201impl Default for NotificationsOpts {
202 fn default() -> Self {
203 fn default_fmt(rec: Record) -> Text {
204 match rec.level() {
205 Level::Error | Level::Warn | Level::Debug => {
206 txt!(
207 "[buffer]{}[notifs.colon]:[] {}",
208 rec.location(),
209 rec.text().clone()
210 )
211 }
212 Level::Info => rec.text().clone(),
213 Level::Trace => unreachable!(),
214 }
215 }
216 fn default_get_mask(rec: Record) -> &'static str {
217 match rec.level() {
218 context::Level::Error => "error",
219 context::Level::Warn => "warn",
220 context::Level::Info => "info",
221 context::Level::Debug => "debug",
222 context::Level::Trace => unreachable!(),
223 }
224 }
225
226 Self {
227 fmt: Box::new(default_fmt),
228 get_mask: Box::new(default_get_mask),
229 allowed_levels: vec![Level::Error, Level::Warn, Level::Info],
230 request_width: false,
231 }
232 }
233}