use std::{
marker::PhantomData,
sync::atomic::{AtomicBool, Ordering},
};
use duat_core::{
context::{Level, Record},
form::Painter,
hook::KeysSent,
prelude::*,
};
pub struct Notifications<U> {
logs: context::Logs,
text: Text,
format_rec: Box<dyn FnMut(Record) -> Text + Send>,
levels: Vec<Level>,
last_rec: Option<usize>,
get_mask: Box<dyn FnMut(Record) -> &'static str + Send>,
_ghost: PhantomData<U>,
}
static CLEAR_NOTIFS: AtomicBool = AtomicBool::new(false);
impl<U: Ui> Widget<U> for Notifications<U> {
type Cfg = NotificationsCfg<U>;
fn cfg() -> Self::Cfg {
Self::Cfg {
format_rec: Box::new(|rec| {
txt!(
"[notifs.target]{}[notifs.colon]: {}",
rec.target(),
rec.text().clone()
)
.build()
}),
get_mask: Box::new(|rec| match rec.level() {
context::Level::Error => "error",
context::Level::Warn => "warn",
context::Level::Info => "info",
context::Level::Debug => "debug",
context::Level::Trace => unreachable!(),
}),
levels: vec![Level::Info, Level::Warn, Level::Error],
_ghost: PhantomData,
}
}
fn update(pa: &mut Pass, handle: &Handle<Self, U>) {
let clear_notifs = CLEAR_NOTIFS.swap(false, Ordering::Relaxed);
let notifs = handle.write(pa);
if notifs.logs.has_changed()
&& let Some((i, rec)) = notifs.logs.last_with_levels(¬ifs.levels)
&& notifs.last_rec.is_none_or(|last_i| last_i < i)
{
handle.set_mask((notifs.get_mask)(rec.clone()));
notifs.text = (notifs.format_rec)(rec);
notifs.last_rec = Some(i);
} else if clear_notifs {
handle.set_mask("");
notifs.text = Text::new()
}
}
fn text(&self) -> &Text {
&self.text
}
fn text_mut(&mut self) -> &mut Text {
&mut self.text
}
fn once() -> Result<(), Text> {
form::set_weak("default.Notifications.error", Form::red());
form::set_weak("accent.error", Form::red().underlined().bold());
form::set_weak("default.Notifications.info", Form::cyan());
form::set_weak("accent.info", Form::blue().underlined().bold());
hook::add_grouped::<KeysSent, U>("RemoveNotificationsOnInput", |_, _| {
CLEAR_NOTIFS.store(true, Ordering::Relaxed);
});
Ok(())
}
fn needs_update(&self, _: &Pass) -> bool {
self.logs.has_changed() || CLEAR_NOTIFS.load(Ordering::Relaxed)
}
fn print(&mut self, painter: Painter, area: &<U as Ui>::Area) {
let cfg = self.print_cfg();
area.print(self.text_mut(), cfg, painter)
}
}
#[doc(hidden)]
pub struct NotificationsCfg<U> {
format_rec: Box<dyn FnMut(Record) -> Text + Send>,
get_mask: Box<dyn FnMut(Record) -> &'static str + Send>,
levels: Vec<Level>,
_ghost: PhantomData<U>,
}
impl<U> NotificationsCfg<U> {
pub fn formatted<T: Into<Text>>(
self,
mut fmt: impl FnMut(Record) -> T + Send + 'static,
) -> Self {
Self {
format_rec: Box::new(move |rec| fmt(rec).into()),
..self
}
}
pub fn filter_levels(mut self, levels: impl IntoIterator<Item = Level>) -> Self {
self.levels = levels.into_iter().collect();
self
}
pub fn with_mask(self, get_mask: impl FnMut(Record) -> &'static str + Send + 'static) -> Self {
Self { get_mask: Box::new(get_mask), ..self }
}
}
impl<U: Ui> WidgetCfg<U> for NotificationsCfg<U> {
type Widget = Notifications<U>;
fn build(self, _: &mut Pass, _: BuildInfo<U>) -> (Self::Widget, PushSpecs) {
let widget = Notifications {
logs: context::logs(),
text: Text::new(),
format_rec: self.format_rec,
get_mask: self.get_mask,
levels: self.levels,
last_rec: None,
_ghost: PhantomData,
};
(widget, PushSpecs::below().ver_len(1.0))
}
}
impl<U: Ui> Default for NotificationsCfg<U> {
fn default() -> Self {
Notifications::cfg()
}
}