use std::sync::Mutex;
use duat_core::{
Ns,
context::{self, Handle, Level, Record},
data::Pass,
hook::{self, KeyTyped, MsgLogged},
text::{Mask, Text, TextMut},
ui::{PushSpecs, PushTarget, Side, Widget},
};
pub fn add_notifications_hook() {
hook::add::<MsgLogged>(|pa, rec| {
let Some(notifications) = context::handle_of::<Notifications>(pa) else {
return;
};
let notifs = notifications.write(pa);
if !notifs.levels.contains(&rec.level()) {
return;
}
let mut global_fmt = GLOBAL_FMT.lock().unwrap();
let mut global_get_mask = GLOBAL_GET_MASK.lock().unwrap();
let mask = if let Some(get_mask) = notifs.get_mask.as_mut() {
get_mask(rec.clone())
} else if let Some(get_mask) = global_get_mask.as_mut() {
get_mask(rec.clone())
} else {
default_get_mask(rec.clone())
};
notifs.text = if let Some(fmt) = notifs.fmt.as_mut() {
fmt(rec)
} else if let Some(fmt) = global_fmt.as_mut() {
fmt(rec)
} else {
default_fmt(rec)
};
notifs.text.insert_tag(Ns::basic(), .., Mask(mask));
if notifs.request_width {
let notifs = notifications.read(pa);
let size = notifications
.area()
.size_of_text(pa, notifs.print_opts(), ¬ifs.text)
.unwrap();
notifications.area().set_width(pa, size.x).unwrap();
notifications.area().set_height(pa, size.y).unwrap();
}
});
hook::add::<KeyTyped>(|pa, _| {
for notifications in context::windows().handles_of::<Notifications>(pa) {
let (notifs, area) = notifications.write_with_area(pa);
if !notifs.text.is_empty_empty() {
notifs.text = Text::new();
if notifs.request_width {
let size = area
.size_of_text(notifs.print_opts(), ¬ifs.text)
.unwrap();
area.set_width(size.x).unwrap();
area.set_height(size.y).unwrap();
}
}
}
})
.lateness(0);
}
pub struct Notifications {
text: Text,
fmt: Option<Box<dyn FnMut(Record) -> Text + Send>>,
levels: Vec<Level>,
get_mask: Option<Box<dyn FnMut(Record) -> &'static str + Send>>,
request_width: bool,
}
#[allow(clippy::type_complexity)]
static GLOBAL_FMT: Mutex<Option<Box<dyn FnMut(Record) -> Text + Send>>> = Mutex::new(None);
#[allow(clippy::type_complexity)]
static GLOBAL_GET_MASK: Mutex<Option<Box<dyn FnMut(Record) -> &'static str + Send>>> =
Mutex::new(None);
impl Notifications {
pub fn builder() -> NotificationsOpts {
NotificationsOpts::default()
}
}
impl Widget for Notifications {
fn text(&self) -> &Text {
&self.text
}
fn text_mut(&mut self) -> TextMut<'_> {
self.text.as_mut()
}
}
#[doc(hidden)]
#[derive(Clone)]
pub struct NotificationsOpts {
allowed_levels: Vec<Level>,
request_width: bool,
}
impl NotificationsOpts {
pub fn push_on(self, pa: &mut Pass, push_target: &impl PushTarget) -> Handle<Notifications> {
let notifications = Notifications {
text: Text::new(),
fmt: None,
get_mask: None,
levels: self.allowed_levels,
request_width: self.request_width,
};
let specs = PushSpecs {
side: Side::Below,
height: Some(1.0),
..Default::default()
};
push_target.push_inner(pa, notifications, specs)
}
pub fn fmt(&mut self, fmt: impl FnMut(Record) -> Text + Send + 'static) {
*GLOBAL_FMT.lock().unwrap() = Some(Box::new(fmt));
}
pub fn set_mask(&mut self, get_mask: impl FnMut(Record) -> &'static str + Send + 'static) {
*GLOBAL_GET_MASK.lock().unwrap() = Some(Box::new(get_mask));
}
pub fn set_allowed_levels(&mut self, levels: impl IntoIterator<Item = Level>) {
self.allowed_levels = levels.into_iter().collect();
}
pub(crate) fn request_width(&mut self) {
self.request_width = true;
}
}
impl Default for NotificationsOpts {
fn default() -> Self {
Self {
allowed_levels: vec![Level::Error, Level::Warn, Level::Info],
request_width: false,
}
}
}
fn default_fmt(rec: Record) -> Text {
match rec.level() {
Level::Error | Level::Warn | Level::Debug => rec.text().clone(),
Level::Info => rec.text().clone(),
Level::Trace => unreachable!(),
}
}
fn default_get_mask(rec: Record) -> &'static str {
match rec.level() {
context::Level::Error => "error",
context::Level::Warn => "warn",
context::Level::Info => "info",
context::Level::Debug => "debug",
context::Level::Trace => unreachable!(),
}
}