duat_utils/widgets/
notifications.rs1use std::{
11 marker::PhantomData,
12 sync::atomic::{AtomicBool, Ordering},
13};
14
15use duat_core::{
16 context::{Level, Record},
17 form::Painter,
18 hook::KeysSent,
19 prelude::*,
20};
21
22pub struct Notifications<U> {
57 logs: context::Logs,
58 text: Text,
59 format_rec: Box<dyn FnMut(Record) -> Text + Send>,
60 levels: Vec<Level>,
61 last_rec: Option<usize>,
62 get_mask: Box<dyn FnMut(Record) -> &'static str + Send>,
63 _ghost: PhantomData<U>,
64}
65
66static CLEAR_NOTIFS: AtomicBool = AtomicBool::new(false);
67
68impl<U: Ui> Widget<U> for Notifications<U> {
69 type Cfg = NotificationsCfg<U>;
70
71 fn cfg() -> Self::Cfg {
72 Self::Cfg {
73 format_rec: Box::new(|rec| {
74 txt!(
75 "[notifs.target]{}[notifs.colon]: {}",
76 rec.target(),
77 rec.text().clone()
78 )
79 .build()
80 }),
81 get_mask: Box::new(|rec| match rec.level() {
82 context::Level::Error => "error",
83 context::Level::Warn => "warn",
84 context::Level::Info => "info",
85 context::Level::Debug => "debug",
86 context::Level::Trace => unreachable!(),
87 }),
88 levels: vec![Level::Info, Level::Warn, Level::Error],
89 _ghost: PhantomData,
90 }
91 }
92
93 fn update(pa: &mut Pass, handle: &Handle<Self, U>) {
94 let clear_notifs = CLEAR_NOTIFS.swap(false, Ordering::Relaxed);
95 let notifs = handle.write(pa);
96
97 if notifs.logs.has_changed()
98 && let Some((i, rec)) = notifs.logs.last_with_levels(¬ifs.levels)
99 && notifs.last_rec.is_none_or(|last_i| last_i < i)
100 {
101 handle.set_mask((notifs.get_mask)(rec.clone()));
102 notifs.text = (notifs.format_rec)(rec);
103 notifs.last_rec = Some(i);
104 } else if clear_notifs {
105 handle.set_mask("");
106 notifs.text = Text::new()
107 }
108 }
109
110 fn text(&self) -> &Text {
111 &self.text
112 }
113
114 fn text_mut(&mut self) -> &mut Text {
115 &mut self.text
116 }
117
118 fn once() -> Result<(), Text> {
119 form::set_weak("default.Notifications.error", Form::red());
120 form::set_weak("accent.error", Form::red().underlined().bold());
121 form::set_weak("default.Notifications.info", Form::cyan());
122 form::set_weak("accent.info", Form::blue().underlined().bold());
123
124 hook::add_grouped::<KeysSent, U>("RemoveNotificationsOnInput", |_, _| {
125 CLEAR_NOTIFS.store(true, Ordering::Relaxed);
126 });
127 Ok(())
128 }
129
130 fn needs_update(&self, _: &Pass) -> bool {
131 self.logs.has_changed() || CLEAR_NOTIFS.load(Ordering::Relaxed)
132 }
133
134 fn print(&mut self, painter: Painter, area: &<U as Ui>::Area) {
135 let cfg = self.print_cfg();
136 area.print(self.text_mut(), cfg, painter)
137 }
138}
139
140#[doc(hidden)]
152pub struct NotificationsCfg<U> {
153 format_rec: Box<dyn FnMut(Record) -> Text + Send>,
154 get_mask: Box<dyn FnMut(Record) -> &'static str + Send>,
155 levels: Vec<Level>,
156 _ghost: PhantomData<U>,
157}
158
159impl<U> NotificationsCfg<U> {
160 pub fn formatted<T: Into<Text>>(
168 self,
169 mut fmt: impl FnMut(Record) -> T + Send + 'static,
170 ) -> Self {
171 Self {
172 format_rec: Box::new(move |rec| fmt(rec).into()),
173 ..self
174 }
175 }
176
177 pub fn filter_levels(mut self, levels: impl IntoIterator<Item = Level>) -> Self {
182 self.levels = levels.into_iter().collect();
183 self
184 }
185
186 pub fn with_mask(self, get_mask: impl FnMut(Record) -> &'static str + Send + 'static) -> Self {
190 Self { get_mask: Box::new(get_mask), ..self }
191 }
192}
193
194impl<U: Ui> WidgetCfg<U> for NotificationsCfg<U> {
195 type Widget = Notifications<U>;
196
197 fn build(self, _: &mut Pass, _: BuildInfo<U>) -> (Self::Widget, PushSpecs) {
198 let widget = Notifications {
199 logs: context::logs(),
200 text: Text::new(),
201 format_rec: self.format_rec,
202 get_mask: self.get_mask,
203 levels: self.levels,
204 last_rec: None,
205 _ghost: PhantomData,
206 };
207
208 (widget, PushSpecs::below().ver_len(1.0))
209 }
210}
211
212impl<U: Ui> Default for NotificationsCfg<U> {
213 fn default() -> Self {
214 Notifications::cfg()
215 }
216}