diff --git a/src/bin.rs b/src/bin.rs index fbdfe6980..e685f093c 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -30,8 +30,6 @@ pub use melib::*; use std::sync::mpsc::{sync_channel, SyncSender, Receiver}; use std::thread; use std::io::{stdout, stdin, }; -use std::collections::VecDeque; -use std::time::{Duration, Instant}; fn main() { /* Lock all stdios */ @@ -48,91 +46,23 @@ fn main() { let (sender, receiver): (SyncSender, Receiver) = sync_channel(::std::mem::size_of::()); { - let mut cmd_queue = VecDeque::with_capacity(5); let sender = sender.clone(); thread::Builder::new().name("input-thread".to_string()).spawn(move || { - get_events(stdin, move | k| { - //eprintln!("{:?}: queue is {:?}", Instant::now(), cmd_queue); - let front: Option<(Instant, char)> = cmd_queue.front().map(|v: &(Instant, char)| { v.clone() }); - let back: Option<(Instant, char)> = cmd_queue.back().map(|v: &(Instant, char)| { v.clone() }); - let mut push: Option<(Instant, char)> = None; - - if let Key::Char(v) = k { - if v == 'g' { - //eprintln!("{:?}: got 'g' in thread",Instant::now()); - push = Some((Instant::now(), v)); - } else if v > '/' && v < ':' { - //eprintln!("{:?}: got '{}' in thread", Instant::now(), v); - if let Some((_, 'g')) = front { - //eprintln!("{:?}: 'g' is front", Instant::now()); - match back { - Some((i, cmd)) if cmd != 'g' => { - let (i, cmd) = back.unwrap(); - let n = cmd as u8; - //eprintln!("{:?}: check for num c={}, n={}", Instant::now(),cmd, n); - if n > 0x2f && n < 0x3a { - //eprintln!("{:?}: got a num {}", Instant::now(), cmd); - let now = Instant::now(); - if now - i < Duration::from_millis(300) { - push = Some((now,cmd)); - let ten_millis = Duration::from_millis(10); - - return; - } - } - }, - Some((i, cmd)) => { - let n = v as u8; - //eprintln!("{:?}: check for num c={}, n={}", Instant::now(),v, n); - if n > 0x2f && n < 0x3a { - //eprintln!("{:?}: got a num {}", Instant::now(), v); - let now = Instant::now(); - if now - i < Duration::from_millis(300) { - push = Some((now,v)); - } - cmd_queue.pop_front(); - let mut s = String::with_capacity(3); - for (_, c) in cmd_queue.iter() { - s.push(*c); - } - s.push(v); - let times = s.parse::(); - //eprintln!("{:?}: parsed {:?}", Instant::now(), times); - if let Ok(g) = times { - sender.send(ThreadEvent::GoCmd(g)).unwrap(); - return; - - } - } - }, - None => {}, - } - - - } - } - if let Some(v) = push { - cmd_queue.push_back(v); - return; - - - } - } - if push.is_none() {sender.send(ThreadEvent::Input(k)).unwrap();} + get_events(stdin, move | k| { sender.send(ThreadEvent::Input(k)).unwrap(); })}).unwrap(); } /* - let folder_length = set.accounts["test_account"].folders.len(); - let mut account = Account::new("test_account".to_string(), set.accounts["test_account"].clone(), backends); - - { - let sender = sender.clone(); - account.watch(RefreshEventConsumer::new(Box::new(move |r| { - sender.send(ThreadEvent::from(r)).unwrap(); - }))); - } - */ + let folder_length = set.accounts["test_account"].folders.len(); + let mut account = Account::new("test_account".to_string(), set.accounts["test_account"].clone(), backends); + + { + let sender = sender.clone(); + account.watch(RefreshEventConsumer::new(Box::new(move |r| { + sender.send(ThreadEvent::from(r)).unwrap(); + }))); + } + */ let mut state = State::new(_stdout); let menu = Entity {component: Box::new(AccountMenu::new(&state.context.accounts)) }; @@ -145,6 +75,7 @@ fn main() { let mut idxa = 0; let mut idxm = 0; let account_length = state.context.accounts.len(); + let mut mode: UIMode = UIMode::Normal; 'main: loop { state.refresh_mailbox(idxa,idxm); let folder_length = state.context.accounts[idxa].len(); @@ -153,53 +84,74 @@ fn main() { 'inner: loop { match receiver.recv().unwrap() { ThreadEvent::Input(k) => { - match k { - key @ Key::Char('j') | key @ Key::Char('k') => { - state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Input(key)}); - state.redraw(); + match mode { + UIMode::Normal => { + match k { + key @ Key::Char('j') | key @ Key::Char('k') => { + state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Input(key)}); + state.redraw(); + }, + key @ Key::Up | key @ Key::Down => { + state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Input(key)}); + state.redraw(); + } + Key::Char('\n') => { + state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Input(Key::Char('\n'))}); + state.redraw(); + } + Key::Char('i') | Key::Esc => { + state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Input(Key::Esc)}); + state.redraw(); + } + Key::F(_) => { + }, + Key::Char('q') | Key::Char('Q') => { + break 'main; + }, + Key::Char('J') => if idxm + 1 < folder_length { + idxm += 1; + break 'inner; + }, + Key::Char('K') => if idxm > 0 { + idxm -= 1; + break 'inner; + }, + Key::Char('l') => if idxa + 1 < account_length { + idxa += 1; + idxm = 0; + break 'inner; + }, + Key::Char('h') => if idxa > 0 { + idxa -= 1; + idxm = 0; + break 'inner; + }, + Key::Char('r') => { + state.update_size(); + state.render(); + }, + Key::Char(' ') => { + mode = UIMode::Execute; + state.rcv_event(UIEvent { id: 0, event_type: UIEventType::ChangeMode(mode)}); + state.redraw(); + } + _ => {} + } }, - key @ Key::Up | key @ Key::Down => { - state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Input(key)}); - state.redraw(); - } - Key::Char('\n') => { - state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Input(Key::Char('\n'))}); - state.redraw(); - } - Key::Char('i') | Key::Esc => { - state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Input(Key::Esc)}); - state.redraw(); - } - Key::F(_) => { + UIMode::Execute => { + match k { + Key::Char('\n') | Key::Esc => { + mode = UIMode::Normal; + state.rcv_event(UIEvent { id: 0, event_type: UIEventType::ChangeMode(mode)}); + state.render(); + }, + k @ Key::Char(_) => { + state.rcv_event(UIEvent { id: 0, event_type: UIEventType::ExInput(k)}); + state.redraw(); + }, + _ => {}, + } }, - Key::Char('q') | Key::Char('Q') => { - break 'main; - }, - Key::Char('J') => if idxm + 1 < folder_length { - idxm += 1; - break 'inner; - }, - Key::Char('K') => if idxm > 0 { - idxm -= 1; - break 'inner; - }, - Key::Char('l') => if idxa + 1 < account_length { - idxa += 1; - idxm = 0; - break 'inner; - }, - Key::Char('h') => if idxa > 0 { - idxa -= 1; - idxm = 0; - break 'inner; - }, - Key::Char('r') => { - state.update_size(); - state.render(); - }, - Key::Char(v) if v > '/' && v < ':' => { - }, - _ => {} } }, ThreadEvent::RefreshMailbox { name : n } => { @@ -209,9 +161,6 @@ fn main() { state.rcv_event(UIEvent { id: 0, event_type: e}); state.render(); }, - ThreadEvent::GoCmd(v) => { - eprintln!("got go cmd with {:?}", v); - }, } } } diff --git a/src/ui/components/mail.rs b/src/ui/components/mail.rs index 7d15437e0..ddd7fdb2c 100644 --- a/src/ui/components/mail.rs +++ b/src/ui/components/mail.rs @@ -153,14 +153,8 @@ impl MailListing { /// Create a pager for the `Envelope` currently under the cursor. fn draw_mail_view(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { - let upper_left = upper_left!(area); - let bottom_right = bottom_right!(area); - let envelope: &Envelope = &self.mailbox.collection[self.cursor_pos]; - let rows = get_y(bottom_right) - get_y(upper_left); - let cols = get_x(bottom_right) - get_x(upper_left); - self.pager = Some(Pager::new(envelope)); self.pager.as_mut().map(|p| p.draw(grid, area, context)); } @@ -169,13 +163,11 @@ impl MailListing { impl Component for MailListing { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { if !self.unfocused { - if !self.dirty { + if !self.is_dirty() { return; } self.dirty = false; /* Draw the entire list */ - let upper_left = upper_left!(area); - let bottom_right = bottom_right!(area); self.draw_list(grid, area, context); @@ -202,7 +194,7 @@ impl Component for MailListing { } let mid = get_y(upper_left) + total_rows - bottom_entity_rows; - if !self.dirty { + if !self.is_dirty() { if let Some(ref mut p) = self.pager { p.draw(grid, ((get_x(upper_left), get_y(upper_left) + mid + headers_rows + 1), bottom_right), @@ -331,6 +323,9 @@ impl Component for MailListing { self.dirty = true; self.pager = None; }, + UIEventType::ChangeMode(UIMode::Normal) => { + self.dirty = true; + }, _ => { }, } @@ -360,7 +355,7 @@ pub struct AccountMenu { impl AccountMenu { pub fn new(accounts: &Vec) -> Self { - let mut accounts = accounts.iter().enumerate().map(|(i, a)| { + let accounts = accounts.iter().enumerate().map(|(i, a)| { AccountMenuEntry { name: a.get_name().to_string(), index: i, @@ -458,9 +453,10 @@ impl AccountMenu { impl Component for AccountMenu { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { - if !(self.dirty) { + if !self.is_dirty() { return; } + clear_area(grid, area); let upper_left = upper_left!(area); let bottom_right = bottom_right!(area); self.dirty = false; @@ -479,6 +475,9 @@ impl Component for AccountMenu { UIEventType::RefreshMailbox(ref m) => { self.highlight_folder(m); }, + UIEventType::ChangeMode(UIMode::Normal) => { + self.dirty = true; + }, _ => { }, } diff --git a/src/ui/components/utilities.rs b/src/ui/components/utilities.rs index 210294575..0c4efd6a7 100644 --- a/src/ui/components/utilities.rs +++ b/src/ui/components/utilities.rs @@ -1,35 +1,5 @@ use ui::components::*; use ui::cells::*; -///A simple box with borders and no content. -pub struct BoxPanel { -} - -impl Component for BoxPanel { - fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { - let upper_left = upper_left!(area); - let bottom_right = bottom_right!(area); - grid[upper_left].set_ch('u'); - grid[bottom_right].set_ch('b'); - let width = get_x(bottom_right) - get_x(upper_left); - let height = get_y(bottom_right) - get_y(upper_left); - - grid[upper_left].set_ch('┌'); - grid[(get_x(upper_left), get_y(bottom_right))].set_ch(BOTTOM_LEFT_CORNER); - grid[(get_x(bottom_right), get_y(upper_left))].set_ch('┐'); - grid[bottom_right].set_ch('┘'); - for i in get_y(upper_left) + 1..get_y(bottom_right) { - grid[(get_x(upper_left), i)].set_ch('│'); - grid[(get_x(upper_left) + width, i)].set_ch('│'); - } - for i in get_x(upper_left)+1..get_x(bottom_right) { - grid[(i, get_y(upper_left))].set_ch('─'); - grid[(i, get_y(upper_left) + height)].set_ch('─'); - } - } - fn process_event(&mut self, _event: &UIEvent, _context: &mut Context) { - return; - } -} /// A horizontally split in half container. pub struct HSplit { @@ -116,6 +86,8 @@ impl Component for VSplit { for i in get_y(upper_left)..=get_y(bottom_right) { grid[(mid, i)].set_ch(VERT_BOUNDARY); + grid[(mid, i)].set_fg(Color::Default); + grid[(mid, i)].set_bg(Color::Default); } if get_y(bottom_right)> 1 { let c = grid.get(mid, get_y(bottom_right)-1).map(|a| a.ch()).unwrap_or_else(|| ' '); @@ -142,42 +114,6 @@ impl Component for VSplit { } } -/// A box with a text content. -pub struct TextBox { - content: String, -} - -impl TextBox { - pub fn new(s: String) -> Self { - TextBox { - content: s, - } - } -} - -impl Component for TextBox { - fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { - let upper_left = upper_left!(area); - let bottom_right = bottom_right!(area); - let mut x = get_x(upper_left); - let y = get_y(upper_left); - for c in self.content.chars() { - grid[(x,y)].set_ch(c); - x += 1; - if x == get_x(bottom_right) + 1 { - x = get_x(upper_left); - } - - if y == get_y(bottom_right) { - break; - } - } - } - fn process_event(&mut self, _event: &UIEvent, _context: &mut Context) { - return; - } -} - /// A pager for text. /// `Pager` holds its own content in its own `CellBuffer` and when `draw` is called, it draws the /// current view of the text. It is responsible for scrolling etc. @@ -196,12 +132,14 @@ impl Pager { let height = lines.len(); let width = lines.iter().map(|l| l.len()).max().unwrap_or(0); let mut content = CellBuffer::new(width, height, Cell::with_char(' ')); - for (i, l) in lines.iter().enumerate() { - write_string_to_grid(l, - &mut content, - Color::Default, - Color::Default, - ((0, i), (width -1, i))); + if width > 0 { + for (i, l) in lines.iter().enumerate() { + write_string_to_grid(l, + &mut content, + Color::Default, + Color::Default, + ((0, i), (width -1, i))); + } } Pager { cursor_pos: 0, @@ -215,24 +153,24 @@ impl Pager { impl Component for Pager { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { - if !self.dirty { + if !self.is_dirty() { return; } let upper_left = upper_left!(area); let bottom_right = bottom_right!(area); + + self.dirty = false; + if self.height == 0 || self.height == self.cursor_pos { + return; + } clear_area(grid, (upper_left, bottom_right)); context.dirty_areas.push_back((upper_left, bottom_right)); - if self.height == 0 { - return; - } - - let pager_context: usize = context.settings.pager.pager_context; - let pager_stop: bool = context.settings.pager.pager_stop; - let rows = get_y(bottom_right) - get_y(upper_left); - let page_length = rows / self.height; - self.dirty = false; + //let pager_context: usize = context.settings.pager.pager_context; + //let pager_stop: bool = context.settings.pager.pager_stop; + //let rows = get_y(bottom_right) - get_y(upper_left); + //let page_length = rows / self.height; let mut inner_x = 0; let mut inner_y = self.cursor_pos; @@ -267,6 +205,9 @@ impl Component for Pager { self.dirty = true; } }, + UIEventType::ChangeMode(UIMode::Normal) => { + self.dirty = true; + }, _ => { }, } @@ -279,8 +220,10 @@ impl Component for Pager { /// Status bar. pub struct StatusBar { container: Entity, - position: usize, status: String, + ex_buffer: String, + mode: UIMode, + height: usize, dirty: bool, } @@ -288,24 +231,31 @@ impl StatusBar { pub fn new(container: Entity) -> Self { StatusBar { container: container, - position: 0, - status: String::with_capacity(250), + status: String::with_capacity(256), + ex_buffer: String::with_capacity(256), dirty: true, + mode: UIMode::Normal, + height: 1, } } fn draw_status_bar(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { - if !self.dirty { - return; - } - self.dirty = false; clear_area(grid, area); - let x = write_string_to_grid(&self.status, + write_string_to_grid(&self.status, grid, Color::Byte(36), Color::Default, area); context.dirty_areas.push_back(area); } + fn draw_execute_bar(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { + clear_area(grid, area); + write_string_to_grid(&self.ex_buffer, + grid, + Color::Byte(124), + Color::Default, + area); + context.dirty_areas.push_back(area); + } } @@ -313,24 +263,60 @@ impl Component for StatusBar { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { let upper_left = upper_left!(area); let bottom_right = bottom_right!(area); + let total_rows = get_y(bottom_right) - get_y(upper_left); - if total_rows == 0 { + if total_rows <= self.height { return; } + let height = self.height; let _ = self.container.component.draw(grid, - (upper_left, (get_x(bottom_right), get_y(bottom_right)-1)), + (upper_left, (get_x(bottom_right), get_y(bottom_right) - height)), context); + + if !self.is_dirty() { + return; + } + self.dirty = false; self.draw_status_bar(grid, (set_y(upper_left, get_y(bottom_right)), bottom_right), context); + match self.mode { + UIMode::Normal => { + }, + UIMode::Execute => { + self.draw_execute_bar(grid, + (set_y(upper_left, get_y(bottom_right) - height + 1), set_y(bottom_right, get_y(bottom_right) - height+1)), + context); + }, + + } } fn process_event(&mut self, event: &UIEvent, context: &mut Context) { self.container.rcv_event(event, context); match event.event_type { UIEventType::RefreshMailbox(ref m) => { - self.status = format!("Mailbox: {}, Messages: {}, New: {}", m.folder.get_name(), m.collection.len(), m.collection.iter().filter(|e| !e.is_seen()).count()); + self.status = format!("{} |Mailbox: {}, Messages: {}, New: {}", self.mode, m.folder.get_name(), m.collection.len(), m.collection.iter().filter(|e| !e.is_seen()).count()); self.dirty = true; }, + UIEventType::ChangeMode(m) => { + let offset = self.status.find('|').unwrap_or(self.status.len()); + self.status.replace_range(..offset, &format!("{} ", m)); + self.dirty = true; + self.mode = m; + match m { + UIMode::Normal => { + self.height = 1; + self.ex_buffer.clear() + }, + UIMode::Execute => { + self.height = 2; + }, + }; + }, + UIEventType::ExInput(Key::Char(c)) => { + self.dirty = true; + self.ex_buffer.push(c); + }, _ => {}, } } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 1570be561..b1f385dd4 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -29,6 +29,8 @@ extern crate ncurses; extern crate melib; use std::collections::VecDeque; +use std::fmt; + pub use self::position::*; /* Color pairs; foreground && background. */ @@ -57,7 +59,6 @@ pub enum ThreadEvent { /// A watched folder has been refreshed. RefreshMailbox{ name: String }, UIEventType(UIEventType), - GoCmd(usize), //Decode { _ }, // For gpg2 signature check } @@ -93,10 +94,12 @@ pub use self::components::*; #[derive(Debug)] pub enum UIEventType { Input(Key), + ExInput(Key), RefreshMailbox(Mailbox), //Quit? Resize, ChangeMailbox(usize), + ChangeMode(UIMode), } @@ -106,19 +109,27 @@ pub struct UIEvent { pub event_type: UIEventType, } +#[derive(Debug, Clone, Copy)] +pub enum UIMode { + Normal, + Execute, +} + +impl fmt::Display for UIMode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", match *self { + UIMode::Normal => { "NORMAL" }, + UIMode::Execute => { "EX" }, + }) + } +} + pub struct Context { pub accounts: Vec, settings: Settings, - queue: VecDeque, /// Areas of the screen that must be redrawn in the next render dirty_areas: VecDeque, backends: Backends, - -} - -impl Context { - - } pub struct State { @@ -160,7 +171,6 @@ impl State { accounts: settings.accounts.iter().map(|(n, a_s)| { Account::new(n.to_string(), a_s.clone(), &backends) }).collect(), backends: backends, settings: settings, - queue: VecDeque::with_capacity(5), dirty_areas: VecDeque::with_capacity(5), }, }; @@ -187,6 +197,7 @@ impl State { self.draw_entity(i); } let areas: Vec = self.context.dirty_areas.drain(0..).collect(); + eprintln!("redrawing {} areas", areas.len()); /* draw each dirty area */ for a in areas { self.draw_area(a); @@ -202,17 +213,17 @@ impl State { let c = self.grid[(x,y)]; if c.get_bg() != cells::Color::Default { - write!(self.stdout, "{}", termion::color::Bg(c.get_bg().as_termion())); + write!(self.stdout, "{}", termion::color::Bg(c.get_bg().as_termion())).unwrap(); } if c.get_fg() != cells::Color::Default { - write!(self.stdout, "{}", termion::color::Fg(c.get_fg().as_termion())); + write!(self.stdout, "{}", termion::color::Fg(c.get_fg().as_termion())).unwrap(); } write!(self.stdout, "{}",c.ch()).unwrap(); if c.get_bg() != cells::Color::Default { - write!(self.stdout, "{}", termion::color::Bg(termion::color::Reset)); + write!(self.stdout, "{}", termion::color::Bg(termion::color::Reset)).unwrap(); } if c.get_fg() != cells::Color::Default { - write!(self.stdout, "{}", termion::color::Fg(termion::color::Reset)); + write!(self.stdout, "{}", termion::color::Fg(termion::color::Reset)).unwrap(); } } @@ -229,9 +240,7 @@ impl State { let cols = self.cols; let rows = self.rows; - /* Only draw dirty areas */ self.draw_area(((0, 0), (cols-1, rows-1))); - return; } pub fn draw_entity(&mut self, idx: usize) { let entity = &mut self.entities[idx];