From 8c7a0ae540080758cc07a63c5dc0e180b484451a Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Fri, 13 Jul 2018 18:38:57 +0300 Subject: [PATCH] Index scrolling, dummy backends, and some pager settings --- src/bin.rs | 125 +++++++++++++++++++++++++++----- src/conf/mod.rs | 12 ++- src/conf/pager.rs | 15 ++++ src/mailbox/backends/imap.rs | 79 ++++++++++++++++++++ src/mailbox/backends/maildir.rs | 1 + src/mailbox/backends/mbox.rs | 81 +++++++++++++++++++++ src/mailbox/backends/mod.rs | 6 ++ src/ui/components/mail.rs | 106 ++++++++++++++++++--------- src/ui/components/mod.rs | 6 +- src/ui/components/utilities.rs | 6 +- src/ui/mod.rs | 61 +++++++++------- 11 files changed, 408 insertions(+), 90 deletions(-) create mode 100644 src/conf/pager.rs create mode 100644 src/mailbox/backends/imap.rs create mode 100644 src/mailbox/backends/mbox.rs diff --git a/src/bin.rs b/src/bin.rs index 100ea3e4..5fb45fc9 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -30,6 +30,8 @@ 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 */ @@ -38,9 +40,9 @@ fn main() { let stdin = stdin(); let stdin = stdin; /* - let _stderr = stderr(); - let mut _stderr = _stderr.lock(); - */ + let _stderr = stderr(); + let mut _stderr = _stderr.lock(); + */ let set = Settings::new(); @@ -48,16 +50,84 @@ 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, |k| { sender.send(ThreadEvent::Input(k)).unwrap(); }) - }).unwrap(); + 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();} + })}).unwrap(); } //let mailbox = Mailbox::new("/home/epilys/Downloads/rust/nutt/Inbox4"); let mut j = 0; - let folder_length = set.accounts["norn"].folders.len(); - let mut account = Account::new("norn".to_string(), set.accounts["norn"].clone(), backends); + 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| { @@ -69,13 +139,14 @@ fn main() { - let mut state = State::new(_stdout); + let mut state = State::new(_stdout, set); let a = Entity {component: Box::new(AccountMenu::new(&account)) }; let listing = MailListing::new(Mailbox::new_dummy()); let b = Entity { component: Box::new(listing) }; let window = Entity { component: Box::new(VSplit::new(a,b,90)) }; state.register_entity(window); + state.render(); 'main: loop { let mailbox = &mut account[j]; @@ -93,20 +164,20 @@ fn main() { 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.render(); + state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Input(key)}); + state.render(); }, key @ Key::Up | key @ Key::Down => { - state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Input(key)}); - state.render(); - } + state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Input(key)}); + state.render(); + } Key::Char('\n') => { - state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Input(Key::Char('\n'))}); - state.render(); + state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Input(Key::Char('\n'))}); + state.render(); } Key::Char('i') | Key::Esc => { - state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Input(Key::Esc)}); - state.render(); + state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Input(Key::Esc)}); + state.render(); } Key::F(_) => { }, @@ -121,13 +192,33 @@ fn main() { j -= 1; break 'inner; }, + Key::Char(k @ 'g') => { + }, + Key::Char(v) if v > '/' && v < ':' => { + }, _ => {} } }, ThreadEvent::RefreshMailbox { name : n } => { eprintln!("Refresh mailbox {}", n); }, + ThreadEvent::UIEventType(e) => { + state.rcv_event(UIEvent { id: 0, event_type: e}); + state.render(); + }, + ThreadEvent::GoCmd(v) => { + eprintln!("got go cmd with {:?}", v); + }, } + /* + if let Some((inst, 'g')) = cmd_queue.front() { + eprintln!("g at front"); + if (Instant::now - inst >= Duration::from_millis(300)) { + + } + + } + */ } } } diff --git a/src/conf/mod.rs b/src/conf/mod.rs index 0fd072a0..9678d8bd 100644 --- a/src/conf/mod.rs +++ b/src/conf/mod.rs @@ -22,6 +22,11 @@ extern crate config; extern crate xdg; extern crate serde; +pub mod pager; + + +use pager::PagerSettings; + use std::collections::HashMap; use std::io; @@ -63,9 +68,12 @@ struct FileAccount { threaded: bool, } + #[derive(Debug, Deserialize, Default)] struct FileSettings { accounts: HashMap, + #[serde(default)] + pager: PagerSettings, } #[derive(Debug, Clone)] @@ -89,6 +97,8 @@ impl AccountSettings { #[derive(Debug)] pub struct Settings { pub accounts: HashMap, + + pub pager: PagerSettings, } @@ -166,6 +176,6 @@ impl Settings { ); } - Settings { accounts: s } + Settings { accounts: s, pager: fs.pager } } } diff --git a/src/conf/pager.rs b/src/conf/pager.rs new file mode 100644 index 00000000..9f3e16ea --- /dev/null +++ b/src/conf/pager.rs @@ -0,0 +1,15 @@ +fn false_val () -> bool { + false +} + +fn zero_val () -> usize { + 0 +} + +#[derive(Debug, Deserialize, Default)] +pub struct PagerSettings { + #[serde(default = "zero_val")] + pager_context: usize, + #[serde(default = "false_val")] + pager_stop: bool, +} diff --git a/src/mailbox/backends/imap.rs b/src/mailbox/backends/imap.rs new file mode 100644 index 00000000..9dd112b7 --- /dev/null +++ b/src/mailbox/backends/imap.rs @@ -0,0 +1,79 @@ +/* + * meli - mailbox module. + * + * Copyright 2017 Manos Pitsidianakis + * + * This file is part of meli. + * + * meli is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * meli is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with meli. If not, see . + */ + +use mailbox::email::{Envelope, Flag}; +use error::{Result}; +use mailbox::backends::{BackendOp, MailBackend, RefreshEventConsumer}; +use conf::Folder; + + +/// `BackendOp` implementor for Imap +#[derive(Debug, Default, Clone)] +pub struct ImapOp { +} + +impl ImapOp { + pub fn new(path: String) -> Self { + ImapOp { + } + } +} + +impl BackendOp for ImapOp { + fn description(&self) -> String { + unimplemented!(); + } + fn as_bytes(&mut self) -> Result<&[u8]> { + unimplemented!(); + } + fn fetch_headers(&mut self) -> Result<&[u8]> { + unimplemented!(); + } + fn fetch_body(&mut self) -> Result<&[u8]> { + unimplemented!(); + } + fn fetch_flags(&self) -> Flag { + unimplemented!(); + } +} + + +/// Imap backend +#[derive(Debug)] +pub struct ImapType { +} + + +impl MailBackend for ImapType { + fn get(&self, folder: &Folder) -> Result> { + unimplemented!(); + } + fn watch(&self, sender: RefreshEventConsumer, folders: &[Folder]) -> () { + unimplemented!(); + } +} + +impl ImapType { + pub fn new(path: &str) -> Self { + ImapType { + } + } +} diff --git a/src/mailbox/backends/maildir.rs b/src/mailbox/backends/maildir.rs index 3a6ca088..86dacb95 100644 --- a/src/mailbox/backends/maildir.rs +++ b/src/mailbox/backends/maildir.rs @@ -158,6 +158,7 @@ impl MailBackend for MaildirType { continue; } let mut p = PathBuf::from(&f.get_path()); + eprintln!("watching {:?}", f); p.push("cur"); watcher.watch(&p, RecursiveMode::NonRecursive).unwrap(); p.pop(); diff --git a/src/mailbox/backends/mbox.rs b/src/mailbox/backends/mbox.rs new file mode 100644 index 00000000..ad63522f --- /dev/null +++ b/src/mailbox/backends/mbox.rs @@ -0,0 +1,81 @@ +/* + * meli - mailbox module. + * + * Copyright 2017 Manos Pitsidianakis + * + * This file is part of meli. + * + * meli is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * meli is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with meli. If not, see . + */ + +use mailbox::email::{Envelope, Flag}; +use error::{Result}; +use mailbox::backends::{BackendOp, MailBackend, RefreshEventConsumer}; +use conf::Folder; + + + + +/// `BackendOp` implementor for Mbox +#[derive(Debug, Default, Clone)] +pub struct MboxOp { +} + +impl MboxOp { + pub fn new(path: String) -> Self { + MboxOp { + } + } +} + +impl BackendOp for MboxOp { + fn description(&self) -> String { + unimplemented!(); + } + fn as_bytes(&mut self) -> Result<&[u8]> { + unimplemented!(); + } + fn fetch_headers(&mut self) -> Result<&[u8]> { + unimplemented!(); + } + fn fetch_body(&mut self) -> Result<&[u8]> { + unimplemented!(); + } + fn fetch_flags(&self) -> Flag { + unimplemented!(); + } +} + + +/// Mbox backend +#[derive(Debug)] +pub struct MboxType { +} + + +impl MailBackend for MboxType { + fn get(&self, folder: &Folder) -> Result> { + unimplemented!(); + } + fn watch(&self, sender: RefreshEventConsumer, folders: &[Folder]) -> () { + unimplemented!(); + } +} + +impl MboxType { + pub fn new(path: &str) -> Self { + MboxType { + } + } +} diff --git a/src/mailbox/backends/mod.rs b/src/mailbox/backends/mod.rs index ea6ffabc..7cc2a963 100644 --- a/src/mailbox/backends/mod.rs +++ b/src/mailbox/backends/mod.rs @@ -19,10 +19,14 @@ * along with meli. If not, see . */ pub mod maildir; +pub mod mbox; +pub mod imap; use conf::Folder; use mailbox::email::{Envelope, Flag}; use mailbox::backends::maildir::MaildirType; +use mailbox::backends::mbox::MboxType; +use mailbox::backends::imap::ImapType; use error::Result; use std::fmt; @@ -43,6 +47,8 @@ impl Backends { map: FnvHashMap::with_capacity_and_hasher(1, Default::default()) }; b.register("maildir".to_string(), Box::new(|| Box::new(MaildirType::new("")))); + b.register("mbox".to_string(), Box::new(|| Box::new(MboxType::new("")))); + b.register("imap".to_string(), Box::new(|| Box::new(ImapType::new("")))); b } diff --git a/src/ui/components/mail.rs b/src/ui/components/mail.rs index 3f7d7ce3..05623c11 100644 --- a/src/ui/components/mail.rs +++ b/src/ui/components/mail.rs @@ -5,7 +5,7 @@ fn make_entry_string(e: &Envelope, idx: usize) -> String { format!("{} {} {:.85}",idx,&e.get_datetime().format("%Y-%m-%d %H:%M:%S").to_string(),e.get_subject()) } -const MAX_WIDTH: usize = 500; +const MAX_cols: usize = 500; /// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the `Envelope` content in a /// `Pager`. @@ -34,7 +34,7 @@ impl MailListing { cursor_pos: 0, new_cursor_pos: 0, length: length, - content: CellBuffer::new(MAX_WIDTH, length+1, Cell::with_char(' ')), + content: CellBuffer::new(MAX_cols, length+1, Cell::with_char(' ')), dirty: false, unfocused: false, mailbox: mailbox, @@ -52,33 +52,51 @@ impl MailListing { write_string_to_grid(&format!("Folder `{}` is empty.", self.mailbox.folder.get_name()), grid, Color::Default, Color::Default, upper_left, upper_left); return; } + let rows = get_y(bottom_right) - get_y(upper_left) + 1; + let prev_page_no = (self.cursor_pos).wrapping_div(rows); + let page_no = (self.new_cursor_pos).wrapping_div(rows); + + /* If cursor position has changed, remove the highlight from the previous position and * apply it in the new one. */ - if self.cursor_pos != self.new_cursor_pos { + if self.cursor_pos != self.new_cursor_pos && prev_page_no == page_no { for idx in [self.cursor_pos, self.new_cursor_pos].iter() { + if *idx >= self.length { + continue; //bounds check + } let color = if self.cursor_pos == *idx { if *idx % 2 == 0 { Color::Byte(236) } else {Color::Default } } else { Color::Byte(246) }; - let x = write_string_to_grid(&make_entry_string(&self.mailbox.collection[*idx], *idx), grid, Color::Default, color, set_y(upper_left, get_y(upper_left) + *idx), bottom_right); - for x in x..get_x(bottom_right)+1 { - grid[(x,get_y(upper_left)+idx)].set_ch(' '); - grid[(x,get_y(upper_left)+idx)].set_bg(color); + let x = write_string_to_grid(&make_entry_string(&self.mailbox.collection[*idx], *idx), grid, Color::Default, color, set_y(upper_left, get_y(upper_left)+(*idx % rows)), bottom_right); + for x in x..=get_x(bottom_right) { + grid[(x,get_y(upper_left)+(*idx % rows))].set_ch(' '); + grid[(x,get_y(upper_left)+(*idx % rows))].set_bg(color); } } self.cursor_pos = self.new_cursor_pos; return; + } else if self.cursor_pos != self.new_cursor_pos { + self.cursor_pos = self.new_cursor_pos; } - let mut idx = 0; - for y in get_y(upper_left)..get_y(bottom_right) { - if idx == self.length { + let mut idx = page_no*rows; + for y in get_y(upper_left)..=get_y(bottom_right) { + if idx >= self.length { clear_area(grid, set_y(upper_left, y), bottom_right); break; } /* Write an entire line for each envelope entry. */ - let color = if self.cursor_pos == idx { Color::Byte(246) } else { if idx % 2 == 0 { Color::Byte(236) } else {Color::Default } }; + let color = if self.cursor_pos == idx { + Color::Byte(246) + } else { + if idx % 2 == 0 { + Color::Byte(236) + } else { + Color::Default + } + }; let x = write_string_to_grid(&make_entry_string(&self.mailbox.collection[idx], idx), grid, Color::Default, color, set_y(upper_left, y), bottom_right); - for x in x..get_x(bottom_right)+1 { + for x in x..=get_x(bottom_right) { grid[(x,y)].set_ch(' '); grid[(x,y)].set_bg(color); } @@ -91,10 +109,10 @@ impl MailListing { fn draw_mail_view(&mut self, grid: &mut CellBuffer, upper_left: Pos, bottom_right: Pos) { let ref mail = self.mailbox.collection[self.cursor_pos]; - let height = get_y(bottom_right) - get_y(upper_left); - let width = get_x(bottom_right) - get_x(upper_left); + 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(mail, height, width)); + self.pager = Some(Pager::new(mail, rows, cols)); let pager = self.pager.as_mut().unwrap(); pager.draw(grid, upper_left,bottom_right); } @@ -116,8 +134,8 @@ impl Component for MailListing { /* Render the mail body in a pager, basically copy what HSplit does */ let total_rows = get_y(bottom_right) - get_y(upper_left); /* TODO: define ratio in Configuration file */ - let bottom_entity_height = (80*total_rows )/100; - let mid = get_y(upper_left) + total_rows - bottom_entity_height; + let bottom_entity_rows = (80*total_rows )/100; + let mid = get_y(upper_left) + total_rows - bottom_entity_rows; if !self.dirty { if let Some(ref mut p) = self.pager { @@ -126,7 +144,7 @@ impl Component for MailListing { return; } self.dirty = false; - self.draw_list(grid, upper_left, (get_x(bottom_right), get_y(upper_left)+ mid -1)); + self.draw_list(grid, upper_left, (get_x(bottom_right), get_y(upper_left)+ mid-3)); if self.length == 0 { return; } @@ -138,42 +156,42 @@ impl Component for MailListing { } } - for i in get_x(upper_left)..get_x(bottom_right)+1 { + for i in get_x(upper_left)..get_x(bottom_right) { grid[(i, mid)].set_ch('─'); } } - let headers_height: usize = 6; + let headers_rows: usize = 6; /* Draw header */ { let ref mail = self.mailbox.collection[self.cursor_pos]; let x = write_string_to_grid(&format!("Date: {}", mail.get_date_as_str()), grid, Color::Byte(33), Color::Default, set_y(upper_left, mid+1), set_y(upper_left, mid+1)); - for x in x..get_x(bottom_right)+1 { + for x in x..get_x(bottom_right) { grid[(x, mid+1)].set_ch(' '); grid[(x, mid+1)].set_bg(Color::Default); grid[(x, mid+1)].set_fg(Color::Default); } let x = write_string_to_grid(&format!("From: {}", mail.get_from()), grid, Color::Byte(33), Color::Default, set_y(upper_left, mid+2), set_y(upper_left, mid+2)); - for x in x..get_x(bottom_right)+1 { + for x in x..get_x(bottom_right) { grid[(x, mid+2)].set_ch(' '); grid[(x, mid+2)].set_bg(Color::Default); grid[(x, mid+2)].set_fg(Color::Default); } let x = write_string_to_grid(&format!("To: {}", mail.get_to()), grid, Color::Byte(33), Color::Default, set_y(upper_left, mid+3), set_y(upper_left, mid+3)); - for x in x..get_x(bottom_right)+1 { + for x in x..get_x(bottom_right) { grid[(x, mid+3)].set_ch(' '); grid[(x, mid+3)].set_bg(Color::Default); grid[(x, mid+3)].set_fg(Color::Default); } let x = write_string_to_grid(&format!("Subject: {}", mail.get_subject()), grid, Color::Byte(33), Color::Default, set_y(upper_left, mid+4), set_y(upper_left, mid+4)); - for x in x..get_x(bottom_right)+1 { + for x in x..get_x(bottom_right) { grid[(x, mid+4)].set_ch(' '); grid[(x, mid+4)].set_bg(Color::Default); grid[(x, mid+4)].set_fg(Color::Default); } let x = write_string_to_grid(&format!("Message-ID: {}", mail.get_message_id_raw()), grid, Color::Byte(33), Color::Default, set_y(upper_left, mid+5), set_y(upper_left, mid+5)); - for x in x..get_x(bottom_right)+1 { + for x in x..get_x(bottom_right) { grid[(x, mid+5)].set_ch(' '); grid[(x, mid+5)].set_bg(Color::Default); grid[(x, mid+5)].set_fg(Color::Default); @@ -181,7 +199,7 @@ impl Component for MailListing { } /* Draw body */ - self.draw_mail_view(grid, (get_x(upper_left), get_y(upper_left) + mid + headers_height), bottom_right); + self.draw_mail_view(grid, (get_x(upper_left), get_y(upper_left) + mid + headers_rows), bottom_right); } } @@ -194,7 +212,7 @@ impl Component for MailListing { } }, UIEventType::Input(Key::Down) => { - if self.length > 0 && self.cursor_pos < self.length - 1 { + if self.length > 0 && self.new_cursor_pos < self.length - 1 { self.new_cursor_pos += 1; self.dirty = true; } @@ -232,7 +250,7 @@ pub struct AccountMenu { entries: Vec<(usize, Folder)>, dirty: bool, name: String, - + cursor: Option, } impl AccountMenu { @@ -246,8 +264,13 @@ impl AccountMenu { entries: entries, dirty: true, name: account.get_name().to_string(), + cursor: None, } } + fn highlight_folder(&mut self, m: &Mailbox) { + self.dirty = true; + self.cursor = Some(m.folder.get_name().to_string()); + } } impl Component for AccountMenu { @@ -271,8 +294,8 @@ impl Component for AccountMenu { } let mut ind = 0; - let mut depth = String::from(""); - let mut s = String::from(format!("{}\n", self.name)); + let mut depth = String::from(" "); + let mut s = String::from(format!("\n\n {}\n", self.name)); fn pop(depth: &mut String) { depth.pop(); depth.pop(); @@ -289,7 +312,7 @@ impl Component for AccountMenu { fn print(root: usize, parents: &Vec>, depth: &mut String, entries: &Vec<(usize, Folder)>, mut s: String) -> String { let len = s.len(); - s.insert_str(len, &format!("{}: {}\n", &entries[root].1.get_name(), entries[root].0)); + s.insert_str(len, &format!("{}: {}\n ", entries[root].0, &entries[root].1.get_name())); let children_no = entries[root].1.get_children().len(); for (idx, child) in entries[root].1.get_children().iter().enumerate() { let len = s.len(); @@ -306,17 +329,28 @@ impl Component for AccountMenu { } let lines: Vec<&str> = s.lines().collect(); + let lines_len = lines.len(); let mut idx = 0; for y in get_y(upper_left)..get_y(bottom_right) { - if idx == self.entries.len() { + if idx == lines_len { break; } - let s = format!("{}", lines[idx]); - write_string_to_grid(&s, grid, Color::Red, Color::Default, set_y(upper_left, y), bottom_right); + let s = if idx == lines_len - 2 { + format!("{}", lines[idx].replace("├", "└")) + } else { + format!("{}", lines[idx]) + }; + write_string_to_grid(&s, grid, Color::Byte(30), Color::Default, set_y(upper_left, y), bottom_right); idx += 1; } } - fn process_event(&mut self, _event: &UIEvent, _queue: &mut VecDeque) { - return; + fn process_event(&mut self, event: &UIEvent, _queue: &mut VecDeque) { + match event.event_type { + UIEventType::RefreshMailbox(ref m) => { + self.highlight_folder(m); + }, + _ => { + }, + } } } diff --git a/src/ui/components/mod.rs b/src/ui/components/mod.rs index 08a952a3..3d195aa8 100644 --- a/src/ui/components/mod.rs +++ b/src/ui/components/mod.rs @@ -94,7 +94,7 @@ fn write_string_to_grid(s: &str, grid: &mut CellBuffer, fg_color: Color, bg_colo if x == (get_x(bottom_right)) { x = get_x(upper_left); y += 1; - if y > (get_y(bottom_right)) { + if y == (get_y(bottom_right)) { return x; } } @@ -103,8 +103,8 @@ fn write_string_to_grid(s: &str, grid: &mut CellBuffer, fg_color: Color, bg_colo } fn clear_area(grid: &mut CellBuffer, upper_left: Pos, bottom_right: Pos) { - for y in get_y(upper_left)..get_y(bottom_right) { - for x in get_x(upper_left)..get_x(bottom_right) { + for y in get_y(upper_left)..=get_y(bottom_right) { + for x in get_x(upper_left)..=get_x(bottom_right) { grid[(x,y)].set_ch(' '); grid[(x,y)].set_bg(Color::Default); grid[(x,y)].set_fg(Color::Default); diff --git a/src/ui/components/utilities.rs b/src/ui/components/utilities.rs index e6afa26d..a8551fa3 100644 --- a/src/ui/components/utilities.rs +++ b/src/ui/components/utilities.rs @@ -49,15 +49,11 @@ impl HSplit { impl Component for HSplit { fn draw(&mut self, grid: &mut CellBuffer, upper_left: Pos, bottom_right: Pos) { - grid[upper_left].set_ch('u'); - let (a,b) = upper_left; - grid[(a+1,b)].set_ch('h'); - let total_rows = get_y(bottom_right) - get_y(upper_left); let bottom_entity_height = (self.ratio*total_rows )/100; let mid = get_y(upper_left) + total_rows - bottom_entity_height; - for i in get_x(upper_left)..get_x(bottom_right)+1 { + for i in get_x(upper_left)..=get_x(bottom_right) { grid[(i, mid)].set_ch('─'); } let _ = self.top.component.draw(grid, upper_left, (get_x(bottom_right), get_y(upper_left) + mid-1)); diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 5d5f6c6d..cc6a4eac 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -54,6 +54,8 @@ pub enum ThreadEvent { Input(Key), /// A watched folder has been refreshed. RefreshMailbox{ name: String }, + UIEventType(UIEventType), + GoCmd(usize), //Decode { _ }, // For gpg2 signature check } @@ -93,6 +95,7 @@ pub enum UIEventType { RefreshMailbox(Mailbox), //Quit? Resize, + ChangeMailbox(usize), } @@ -103,12 +106,13 @@ pub struct UIEvent { } pub struct State { - width: usize, - height: usize, + cols: usize, + rows: usize, grid: CellBuffer, - pub stdout: termion::raw::RawTerminal, + stdout: termion::raw::RawTerminal, entities: Vec, + settings: Settings, } @@ -120,34 +124,37 @@ impl Drop for State { } impl State { - pub fn new(stdout: W) -> Self { + pub fn new(stdout: W, settings: Settings) -> Self { let termsize = termion::terminal_size().ok(); - let termwidth = termsize.map(|(w,_)| w); - let termheight = termsize.map(|(_,h)| h); - let width = termwidth.unwrap_or(0) as usize; - let height = termheight.unwrap_or(0) as usize; + let termcols = termsize.map(|(w,_)| w); + let termrows = termsize.map(|(_,h)| h); + let cols = termcols.unwrap_or(0) as usize; + let rows = termrows.unwrap_or(0) as usize; let mut s = State { - width: width, - height: height, + cols: cols, + rows: rows, //queue: VecDeque::new(); - grid: CellBuffer::new(width+1, height+1, Cell::with_char(' ')), + grid: CellBuffer::new(cols, rows, Cell::with_char(' ')), stdout: stdout.into_raw_mode().unwrap(), - entities: Vec::with_capacity(2), + entities: Vec::with_capacity(1), + settings: settings, }; write!(s.stdout, "{}{}{}", cursor::Hide, clear::All, cursor::Goto(1,1)).unwrap(); s } - pub fn hello_w(&mut self) { - write!(self.stdout, "Hey there.").unwrap(); - } fn update_size(&mut self) { /* update dimensions. TODO: Only do that in size change events. ie SIGWINCH */ let termsize = termion::terminal_size().ok(); - let termwidth = termsize.map(|(w,_)| w); - let termheight = termsize.map(|(_,h)| h); - self.width = termwidth.unwrap_or(72) as usize; - self.height = termheight.unwrap_or(120) as usize; + let termcols = termsize.map(|(w,_)| w); + let termrows = termsize.map(|(_,h)| h); + if termcols.unwrap_or(72) as usize != self.cols || termrows.unwrap_or(120) as usize != self.rows { + eprintln!("Size updated, from ({}, {}) -> ({:?}, {:?})", self.cols, self.rows, termcols, termrows); + + } + self.cols = termcols.unwrap_or(72) as usize; + self.rows = termrows.unwrap_or(120) as usize; + self.grid.resize(self.cols, self.rows, Cell::with_char(' ')); } pub fn render(&mut self) { @@ -158,9 +165,9 @@ impl State { } /* Only draw dirty areas */ - for y in 0..self.height { - write!(self.stdout, "{}", cursor::Goto(1,y as u16)).unwrap(); - for x in 0..self.width { + for y in 0..self.rows { + write!(self.stdout, "{}", cursor::Goto(1,(y+1) as u16)).unwrap(); + for x in 0..self.cols { let c = self.grid[(x,y)]; if c.get_bg() != cells::Color::Default { @@ -179,13 +186,12 @@ impl State { } } + self.stdout.flush().unwrap(); } pub fn draw_entity(&mut self, idx: usize) { let ref mut entity = self.entities[idx]; - eprintln!("Entity is {:?}", entity); - let upper_left = (1,1); - let bottom_right = (self.width, self.height); - eprintln!("Upper left is {:?} and bottom_right is {:?}", upper_left, bottom_right); + let upper_left = (0,0); + let bottom_right = (self.cols-1, self.rows-1); entity.component.draw(&mut self.grid, upper_left, bottom_right); } @@ -267,12 +273,11 @@ pub enum Key { Esc, } -pub fn get_events(stdin: std::io::Stdin, closure: F) where F: Fn(Key) -> (){ +pub fn get_events(stdin: std::io::Stdin, mut closure: F) where F: FnMut(Key) -> (){ let stdin = stdin.lock(); for c in stdin.keys() { if let Ok(k) = c { let k = convert_key(k); - eprintln!("Received key: {:?}", k); closure(k); } }