use std::io;
use std::rc::Rc;
use std::time::{Duration, Instant};
use crossterm::{
cursor,
event::{self, KeyCode},
execute, queue, style,
style::Stylize,
};
use super::AppColors;
#[derive(Debug, Clone, PartialEq)]
struct Notification {
message: String,
error: bool,
expiry: Option<Instant>,
}
impl Notification {
pub fn new(message: String, error: bool, expiry: Option<Instant>) -> Self {
return Self {
message: message,
error: error,
expiry: expiry,
};
}
}
#[derive(Debug)]
pub struct NotifWin {
colors: Rc<AppColors>,
start_y: u16,
total_rows: u16,
total_cols: u16,
msg_stack: Vec<Notification>,
persistent_msg: Option<Notification>,
current_msg: Option<Notification>,
}
impl NotifWin {
pub fn new(colors: Rc<AppColors>, start_y: u16, total_rows: u16, total_cols: u16) -> Self {
return Self {
colors: colors,
start_y: start_y,
total_rows: total_rows,
total_cols: total_cols,
msg_stack: Vec::new(),
persistent_msg: None,
current_msg: None,
};
}
pub fn redraw(&self) {
let empty = vec![" "; self.total_cols as usize];
let empty_string = empty.join("");
queue!(
io::stdout(),
cursor::MoveTo(0, self.start_y),
style::PrintStyledContent(
style::style(&empty_string)
.with(self.colors.normal.0)
.on(self.colors.normal.1)
),
)
.unwrap();
}
pub fn check_notifs(&mut self) {
if !self.msg_stack.is_empty() {
let now = Instant::now();
self.msg_stack.retain(|x| match x.expiry {
Some(exp) => now < exp,
None => true,
});
if !self.msg_stack.is_empty() {
let last_item = &self.msg_stack[self.msg_stack.len() - 1];
match &self.current_msg {
Some(curr) => {
if last_item != curr {
self.display_notif(last_item);
}
}
None => self.display_notif(last_item),
};
self.current_msg = Some(last_item.clone());
} else if let Some(msg) = &self.persistent_msg {
match &self.current_msg {
Some(curr) => {
if msg != curr {
self.display_notif(msg);
}
}
None => self.display_notif(msg),
};
self.current_msg = Some(msg.clone());
} else {
self.redraw();
self.current_msg = None;
}
}
}
pub fn input_notif(&self, prefix: &str) -> String {
execute!(
io::stdout(),
cursor::MoveTo(0, self.start_y),
style::Print(&prefix),
cursor::Show
)
.unwrap();
let mut inputs = String::new();
let mut cancelled = false;
let min_x = prefix.len() as u16;
let mut current_max_x = prefix.len() as u16;
let mut cursor_x = prefix.len() as u16;
loop {
if let event::Event::Key(input) = event::read().expect("") {
let cursor_idx = (cursor_x - min_x) as usize;
match input.code {
KeyCode::Esc | KeyCode::Char('\u{1b}') => {
cancelled = true;
break;
}
KeyCode::Enter | KeyCode::Char('\n') => {
break;
}
KeyCode::Backspace | KeyCode::Char('\u{7f}') => {
if current_max_x > min_x {
current_max_x -= 1;
cursor_x -= 1;
let _ = inputs.remove(cursor_idx - 1);
execute!(io::stdout(), cursor::MoveLeft(1)).unwrap();
for i in inputs.chars().skip(cursor_idx - 1) {
execute!(io::stdout(), style::Print(i)).unwrap();
}
execute!(
io::stdout(),
style::Print(" "),
cursor::MoveTo(cursor_x, self.start_y)
)
.unwrap();
}
}
KeyCode::Delete => {
if cursor_x < current_max_x {
current_max_x -= 1;
let _ = inputs.remove(cursor_idx);
for i in inputs.chars().skip(cursor_idx) {
execute!(io::stdout(), style::Print(i)).unwrap();
}
execute!(
io::stdout(),
style::Print(" "),
cursor::MoveTo(cursor_x, self.start_y)
)
.unwrap();
}
}
KeyCode::Left => {
if cursor_x > min_x {
cursor_x -= 1;
execute!(io::stdout(), cursor::MoveLeft(1)).unwrap();
}
}
KeyCode::Right => {
if cursor_x < current_max_x {
cursor_x += 1;
execute!(io::stdout(), cursor::MoveRight(1)).unwrap();
}
}
KeyCode::Char(c) => {
current_max_x += 1;
cursor_x += 1;
if cursor_x < current_max_x {
inputs.insert(cursor_idx, c);
for i in inputs.chars().skip(cursor_idx) {
execute!(io::stdout(), style::Print(i)).unwrap();
}
execute!(io::stdout(), cursor::MoveTo(cursor_x, self.start_y)).unwrap();
} else {
inputs.push(c);
execute!(io::stdout(), style::Print(c)).unwrap();
}
}
_ => (),
}
}
}
execute!(io::stdout(), cursor::Hide).unwrap();
self.redraw();
if cancelled {
return String::from("");
}
return inputs;
}
fn display_notif(&self, notif: &Notification) {
self.redraw();
let styled = if notif.error {
style::style(¬if.message)
.with(self.colors.error.0)
.on(self.colors.error.1)
.attribute(style::Attribute::Bold)
} else {
style::style(¬if.message)
.with(self.colors.normal.0)
.on(self.colors.normal.1)
};
queue!(
io::stdout(),
cursor::MoveTo(0, self.start_y),
style::PrintStyledContent(styled)
)
.unwrap();
}
pub fn timed_notif(&mut self, message: String, duration: u64, error: bool) {
let expiry = Instant::now() + Duration::from_millis(duration);
self.msg_stack
.push(Notification::new(message, error, Some(expiry)));
}
pub fn persistent_notif(&mut self, message: String, error: bool) {
let notif = Notification::new(message, error, None);
self.persistent_msg = Some(notif.clone());
if self.msg_stack.is_empty() {
self.display_notif(¬if);
self.current_msg = Some(notif);
}
}
pub fn clear_persistent_notif(&mut self) {
self.persistent_msg = None;
if self.msg_stack.is_empty() {
self.redraw();
self.current_msg = None;
}
}
pub fn resize(&mut self, total_rows: u16, total_cols: u16) {
self.total_rows = total_rows;
self.total_cols = total_cols;
self.redraw();
if let Some(curr) = &self.current_msg {
self.display_notif(curr);
}
}
}