ui: add floating notifications within terminal

`DisplayMessage` messages are for user input responses (eg errors for
user actions). They now appear as floating boxes in the bottom right
corner of the UI and can be browsed with Alt('<') and Alt('>')
async
Manos Pitsidianakis 2020-02-06 21:55:04 +02:00
parent 4a4c8e265a
commit 3bca6d1d9c
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
1 changed files with 60 additions and 2 deletions

View File

@ -34,6 +34,7 @@ use melib::backends::{FolderHash, NotifyFn};
use crossbeam::channel::{unbounded, Receiver, Sender};
use fnv::FnvHashMap;
use smallvec::SmallVec;
use std::env;
use std::io::Write;
use std::result;
@ -185,6 +186,17 @@ pub struct State {
components: Vec<Box<dyn Component>>,
pub context: Context,
timer: thread::JoinHandle<()>,
display_messages: SmallVec<[DisplayMessage; 8]>,
display_messages_expiration_start: Option<UnixTimestamp>,
display_messages_active: bool,
display_messages_pos: usize,
}
#[derive(Debug)]
struct DisplayMessage {
timestamp: UnixTimestamp,
msg: String,
}
impl Drop for State {
@ -285,6 +297,10 @@ impl State {
} else {
State::draw_horizontal_segment
},
display_messages: SmallVec::new(),
display_messages_expiration_start: None,
display_messages_pos: 0,
display_messages_active: false,
context: Context {
accounts,
@ -457,9 +473,22 @@ impl State {
}
let mut areas: smallvec::SmallVec<[Area; 8]> =
self.context.dirty_areas.drain(0..).collect();
if areas.is_empty() {
return;
if self.display_messages_active {
let now = melib::datetime::now();
if self
.display_messages_expiration_start
.map(|t| t + 5 < now)
.unwrap_or(false)
{
self.display_messages_active = false;
self.display_messages_expiration_start = None;
areas.push((
(0, 0),
(self.cols.saturating_sub(1), self.rows.saturating_sub(1)),
));
}
}
/* Sort by x_start, ie upper_left corner's x coordinate */
areas.sort_by(|a, b| (a.0).0.partial_cmp(&(b.0).0).unwrap());
/* draw each dirty area */
@ -798,6 +827,12 @@ impl State {
/// The application's main loop sends `UIEvents` to state via this method.
pub fn rcv_event(&mut self, mut event: UIEvent) {
if let UIEvent::Input(_) = event {
if self.display_messages_expiration_start.is_none() {
self.display_messages_expiration_start = Some(melib::datetime::now());
}
}
match event {
// Command type is handled only by State.
UIEvent::Command(cmd) => {
@ -845,6 +880,29 @@ impl State {
self.redraw();
return;
}
UIEvent::Input(Key::Alt('<')) => {
self.display_messages_expiration_start = Some(melib::datetime::now());
self.display_messages_active = true;
self.display_messages_pos = self.display_messages_pos.saturating_sub(1);
return;
}
UIEvent::Input(Key::Alt('>')) => {
self.display_messages_expiration_start = Some(melib::datetime::now());
self.display_messages_active = true;
self.display_messages_pos = std::cmp::min(
self.display_messages.len().saturating_sub(1),
self.display_messages_pos + 1,
);
return;
}
UIEvent::StatusEvent(StatusEvent::DisplayMessage(ref msg)) => {
self.display_messages.push(DisplayMessage {
timestamp: melib::datetime::now(),
msg: msg.clone(),
});
self.display_messages_active = true;
self.display_messages_pos = self.display_messages.len() - 1;
}
_ => {}
}
/* inform each component */