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> {
55 logs: context::Logs,
56 text: Text,
57 _ghost: PhantomData<U>,
58 format_rec: Box<dyn FnMut(Record) -> Option<Text>>,
59 get_mask: Box<dyn FnMut(Record) -> &'static str>,
60}
61
62static CLEAR_NOTIFS: AtomicBool = AtomicBool::new(false);
63
64impl<U: Ui> Widget<U> for Notifications<U> {
65 type Cfg = NotificationsCfg<U>;
66
67 fn cfg() -> Self::Cfg {
68 NotificationsCfg {
69 format_rec: Box::new(|rec| {
70 (rec.level() < Level::Debug).then(|| {
72 txt!(
73 "[notifs.target]{}[notifs.colon]: {}",
74 rec.target(),
75 rec.text().clone()
76 )
77 .build()
78 })
79 }),
80 get_mask: Box::new(|rec| match rec.level() {
81 context::Level::Error => "error",
82 context::Level::Warn => "warn",
83 context::Level::Info => "info",
84 context::Level::Debug => "debug",
85 context::Level::Trace => unreachable!(),
86 }),
87 _ghost: PhantomData,
88 }
89 }
90
91 fn update(pa: &mut Pass, handle: Handle<Self, U>) {
92 let clear_notifs = CLEAR_NOTIFS.swap(false, Ordering::Relaxed);
93 handle.write(pa, |wid, _| {
94 if wid.logs.has_changed()
95 && let Some(rec) = wid.logs.last()
96 && let Some(text) = (wid.format_rec)(rec.clone())
97 {
98 handle.set_mask((wid.get_mask)(rec));
99 wid.text = text
100 } else if clear_notifs {
101 handle.set_mask("");
102 wid.text = Text::new()
103 }
104 });
105 }
106
107 fn text(&self) -> &Text {
108 &self.text
109 }
110
111 fn text_mut(&mut self) -> &mut Text {
112 &mut self.text
113 }
114
115 fn once() -> Result<(), Text> {
116 form::set_weak("default.Notifications.error", Form::red());
117 form::set_weak("accent.error", Form::red().underlined().bold());
118 form::set_weak("default.Notifications.info", Form::cyan());
119 form::set_weak("accent.info", Form::blue().underlined().bold());
120
121 hook::add_grouped::<KeysSent, U>("RemoveNotificationsOnInput", |_, _| {
122 CLEAR_NOTIFS.store(true, Ordering::Relaxed);
123 });
124 Ok(())
125 }
126
127 fn needs_update(&self) -> bool {
128 self.logs.has_changed() || CLEAR_NOTIFS.load(Ordering::Relaxed)
129 }
130
131 fn print(&mut self, painter: Painter, area: &<U as Ui>::Area) {
132 let cfg = self.print_cfg();
133 area.print(self.text_mut(), cfg, painter)
134 }
135}
136
137#[doc(hidden)]
149pub struct NotificationsCfg<U> {
150 format_rec: Box<dyn FnMut(Record) -> Option<Text>>,
151 get_mask: Box<dyn FnMut(Record) -> &'static str>,
152 _ghost: PhantomData<U>,
153}
154
155impl<U> NotificationsCfg<U> {
156 pub fn formatted<T: Into<Text>>(
162 self,
163 mut format_rec: impl FnMut(Record) -> Option<T> + 'static,
164 ) -> Self {
165 Self {
166 format_rec: Box::new(move |rec| format_rec(rec).map(Into::into)),
167 ..self
168 }
169 }
170
171 pub fn with_mask(self, get_mask: impl FnMut(Record) -> &'static str + 'static) -> Self {
175 Self { get_mask: Box::new(get_mask), ..self }
176 }
177}
178
179impl<U: Ui> WidgetCfg<U> for NotificationsCfg<U> {
180 type Widget = Notifications<U>;
181
182 fn build(self, _: &mut Pass, _: Option<FileHandle<U>>) -> (Self::Widget, PushSpecs) {
183 let widget = Notifications {
184 logs: context::logs(),
185 text: Text::new(),
186 format_rec: self.format_rec,
187 get_mask: self.get_mask,
188 _ghost: PhantomData,
189 };
190
191 (widget, PushSpecs::below().with_ver_len(1.0))
192 }
193}
194
195impl<U: Ui> Default for NotificationsCfg<U> {
196 fn default() -> Self {
197 Notifications::cfg()
198 }
199}