From 3bca6d1d9c48e2b5d764165f02f321021837068d Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Thu, 6 Feb 2020 21:55:04 +0200 Subject: [PATCH] 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('>') --- src/state.rs | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/src/state.rs b/src/state.rs index 107225539..1d1c47c60 100644 --- a/src/state.rs +++ b/src/state.rs @@ -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>, pub context: Context, timer: thread::JoinHandle<()>, + + display_messages: SmallVec<[DisplayMessage; 8]>, + display_messages_expiration_start: Option, + 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 */