diff --git a/ui/src/components/mail/compose.rs b/ui/src/components/mail/compose.rs index ce9033f53..edb30be73 100644 --- a/ui/src/components/mail/compose.rs +++ b/ui/src/components/mail/compose.rs @@ -21,6 +21,7 @@ use super::*; +#[derive(Debug)] pub struct Composer { dirty: bool, mode: ViewMode, @@ -39,6 +40,7 @@ impl Default for Composer { } } +#[derive(Debug)] enum ViewMode { //Compose, Overview, @@ -56,10 +58,6 @@ impl Component for Composer { if self.dirty { clear_area(grid, area); } - if !self.buffer.is_empty() { - eprintln!("{:?}", EnvelopeWrapper::new(self.buffer.as_bytes().to_vec())); - - } let upper_left = upper_left!(area); let bottom_right = bottom_right!(area); @@ -104,7 +102,11 @@ impl Component for Composer { } } - fn process_event(&mut self, event: &UIEvent, context: &mut Context) { + fn process_event(&mut self, event: &UIEvent, context: &mut Context) -> bool { + if self.pager.process_event(event, context) { + return true; + } + match event.event_type { UIEventType::Resize => { self.dirty = true; @@ -135,11 +137,11 @@ impl Component for Composer { self.pager.update_from_string(result); context.restore_input(); self.dirty = true; - return; + return true; }, _ => {}, } - self.pager.process_event(event, context); + false } fn is_dirty(&self) -> bool { diff --git a/ui/src/components/mail/listing/compact.rs b/ui/src/components/mail/listing/compact.rs index 0ccebd720..96ab3d94a 100644 --- a/ui/src/components/mail/listing/compact.rs +++ b/ui/src/components/mail/listing/compact.rs @@ -27,6 +27,7 @@ const MAX_COLS: usize = 500; /// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the `Envelope` content in a /// `ThreadView`. +#[derive(Debug)] pub struct CompactListing { /// (x, y, z): x is accounts, y is folders, z is index inside a folder. cursor_pos: (usize, usize, usize), @@ -301,7 +302,7 @@ impl Component for CompactListing { } return; } - self.view = Some(ThreadView::new(self.cursor_pos)); + self.view = Some(ThreadView::new(self.cursor_pos, context)); self.view.as_mut().unwrap().draw( grid, area, @@ -310,28 +311,37 @@ impl Component for CompactListing { self.dirty = false; } } - fn process_event(&mut self, event: &UIEvent, context: &mut Context) { + fn process_event(&mut self, event: &UIEvent, context: &mut Context) -> bool { + if let Some(ref mut v) = self.view { + if v.process_event(event, context) { + return true; + } + } match event.event_type { UIEventType::Input(Key::Up) => { if self.cursor_pos.2 > 0 { - self.new_cursor_pos.2 -= 1; + self.new_cursor_pos.2 = self.new_cursor_pos.2.saturating_sub(1); self.dirty = true; } + return true; } UIEventType::Input(Key::Down) => { if self.length > 0 && self.new_cursor_pos.2 < self.length - 1 { self.new_cursor_pos.2 += 1; self.dirty = true; } + return true; } UIEventType::Input(Key::Char('\n')) if !self.unfocused => { self.unfocused = true; self.dirty = true; + return true; } UIEventType::Input(Key::Char('i')) if self.unfocused => { self.unfocused = false; self.dirty = true; self.view = None; + return true; } UIEventType::Input(Key::Char(k @ 'J')) | UIEventType::Input(Key::Char(k @ 'K')) => { let folder_length = context.accounts[self.cursor_pos.0].len(); @@ -364,6 +374,7 @@ impl Component for CompactListing { } _ => {} } + return true; } UIEventType::Input(Key::Char(k @ 'h')) | UIEventType::Input(Key::Char(k @ 'l')) => { let accounts_length = context.accounts.len(); @@ -382,6 +393,7 @@ impl Component for CompactListing { } _ => {} } + return true; } UIEventType::RefreshMailbox(_) => { self.dirty = true; @@ -391,7 +403,6 @@ impl Component for CompactListing { if *idxa == self.new_cursor_pos.0 && *idxf == self.new_cursor_pos.1 { self.dirty = true; self.refresh_mailbox(context); - return; } } UIEventType::ChangeMode(UIMode::Normal) => { @@ -408,35 +419,33 @@ impl Component for CompactListing { .toggle_threaded(); self.refresh_mailbox(context); self.dirty = true; - return; + return true; } Action::ViewMailbox(idx) => { self.new_cursor_pos.1 = *idx; self.dirty = true; self.refresh_mailbox(context); - return; + return true; } Action::SubSort(field, order) => { eprintln!("SubSort {:?} , {:?}", field, order); self.subsort = (*field, *order); self.dirty = true; self.refresh_mailbox(context); - return; + return true; } Action::Sort(field, order) => { eprintln!("Sort {:?} , {:?}", field, order); self.sort = (*field, *order); self.dirty = true; self.refresh_mailbox(context); - return; + return true; } // _ => {} }, _ => {} } - if let Some(ref mut v) = self.view { - v.process_event(event, context); - } + false } fn is_dirty(&self) -> bool { self.dirty || self.view.as_ref().map(|p| p.is_dirty()).unwrap_or(false) diff --git a/ui/src/components/mail/listing/mod.rs b/ui/src/components/mail/listing/mod.rs index f6672d18e..132f4408c 100644 --- a/ui/src/components/mail/listing/mod.rs +++ b/ui/src/components/mail/listing/mod.rs @@ -28,6 +28,7 @@ const MAX_COLS: usize = 500; /// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the `Envelope` content in a /// `MailView`. +#[derive(Debug)] pub struct MailListing { /// (x, y, z): x is accounts, y is folders, z is index inside a folder. cursor_pos: (usize, usize, usize), @@ -591,7 +592,23 @@ impl Component for MailListing { } return; } - self.view = Some(MailView::new(self.cursor_pos, self.local_collection.clone(), None, None)); + { + let threaded = context.accounts[self.cursor_pos.0] + .runtime_settings + .conf() + .threaded(); + let account = &context.accounts[self.cursor_pos.0]; + let mailbox = &account[self.cursor_pos.1] + .as_ref() + .unwrap(); + let mut coordinates = self.cursor_pos; + coordinates.2 = if threaded { + mailbox.threaded_mail(self.cursor_pos.2) + } else { + self.local_collection[self.cursor_pos.2] + }; + self.view = Some(MailView::new(coordinates, None, None)); + } self.view.as_mut().unwrap().draw( grid, (set_y(upper_left, mid + 1), bottom_right), @@ -600,23 +617,31 @@ impl Component for MailListing { self.dirty = false; } } - fn process_event(&mut self, event: &UIEvent, context: &mut Context) { + fn process_event(&mut self, event: &UIEvent, context: &mut Context) -> bool { + if let Some(ref mut v) = self.view { + if v.process_event(event, context) { + return true; + } + } match event.event_type { UIEventType::Input(Key::Up) => { if self.cursor_pos.2 > 0 { self.new_cursor_pos.2 -= 1; self.dirty = true; } + return true; } UIEventType::Input(Key::Down) => { if self.length > 0 && self.new_cursor_pos.2 < self.length - 1 { self.new_cursor_pos.2 += 1; self.dirty = true; } + return true; } UIEventType::Input(Key::Char('\n')) if !self.unfocused => { self.unfocused = true; self.dirty = true; + return true; } UIEventType::Input(Key::Char('m')) if !self.unfocused => { use std::process::{Command, Stdio}; @@ -662,12 +687,13 @@ impl Component for MailListing { id: 0, event_type: UIEventType::ChangeMode(UIMode::Fork), }); - return; + return true; } UIEventType::Input(Key::Char('i')) if self.unfocused => { self.unfocused = false; self.dirty = true; self.view = None; + return true; } UIEventType::Input(Key::Char(k @ 'J')) | UIEventType::Input(Key::Char(k @ 'K')) => { let folder_length = context.accounts[self.cursor_pos.0].len(); @@ -700,6 +726,7 @@ impl Component for MailListing { } _ => {} } + return true; } UIEventType::Input(Key::Char(k @ 'h')) | UIEventType::Input(Key::Char(k @ 'l')) => { let accounts_length = context.accounts.len(); @@ -718,6 +745,7 @@ impl Component for MailListing { } _ => {} } + return true; } UIEventType::RefreshMailbox(_) => { self.dirty = true; @@ -727,7 +755,6 @@ impl Component for MailListing { if *idxa == self.new_cursor_pos.0 && *idxf == self.new_cursor_pos.1 { self.dirty = true; self.refresh_mailbox(context); - return; } } UIEventType::ChangeMode(UIMode::Normal) => { @@ -744,35 +771,33 @@ impl Component for MailListing { .toggle_threaded(); self.refresh_mailbox(context); self.dirty = true; - return; + return true; } Action::ViewMailbox(idx) => { self.new_cursor_pos.1 = *idx; self.dirty = true; self.refresh_mailbox(context); - return; + return true; } Action::SubSort(field, order) => { eprintln!("SubSort {:?} , {:?}", field, order); self.subsort = (*field, *order); self.dirty = true; self.refresh_mailbox(context); - return; + return true; } Action::Sort(field, order) => { eprintln!("Sort {:?} , {:?}", field, order); self.sort = (*field, *order); self.dirty = true; self.refresh_mailbox(context); - return; + return true; } // _ => {} }, _ => {} } - if let Some(ref mut v) = self.view { - v.process_event(event, context); - } + false } fn is_dirty(&self) -> bool { self.dirty || self.view.as_ref().map(|p| p.is_dirty()).unwrap_or(false) diff --git a/ui/src/components/mail/mod.rs b/ui/src/components/mail/mod.rs index 5c4e8e852..24d1890d7 100644 --- a/ui/src/components/mail/mod.rs +++ b/ui/src/components/mail/mod.rs @@ -234,7 +234,7 @@ impl Component for AccountMenu { context.dirty_areas.push_back(area); } - fn process_event(&mut self, event: &UIEvent, _context: &mut Context) { + fn process_event(&mut self, event: &UIEvent, _context: &mut Context) -> bool { match event.event_type { UIEventType::RefreshMailbox(c) => { self.cursor = Some(c); @@ -248,6 +248,7 @@ impl Component for AccountMenu { } _ => {} } + false } fn is_dirty(&self) -> bool { self.dirty diff --git a/ui/src/components/mail/view/envelope.rs b/ui/src/components/mail/view/envelope.rs index ac5d73daa..8f0436124 100644 --- a/ui/src/components/mail/view/envelope.rs +++ b/ui/src/components/mail/view/envelope.rs @@ -45,6 +45,7 @@ impl ViewMode { /// Contains an Envelope view, with sticky headers, a pager for the body, and subviews for more /// menus +#[derive(Debug)] pub struct EnvelopeView { pager: Option, subview: Option>, @@ -313,13 +314,24 @@ impl Component for EnvelopeView { } } - fn process_event(&mut self, event: &UIEvent, context: &mut Context) { + fn process_event(&mut self, event: &UIEvent, context: &mut Context) -> bool { + if let Some(ref mut sub) = self.subview { + if sub.process_event(event, context) { + return true; + } + } else if let Some(ref mut p) = self.pager { + if p.process_event(event, context) { + return true; + } + } match event.event_type { UIEventType::Input(Key::Esc) => { self.cmd_buf.clear(); + return true; } UIEventType::Input(Key::Char(c)) if c >= '0' && c <= '9' => { self.cmd_buf.push(c); + return true; } UIEventType::Input(Key::Char('r')) if self.mode == ViewMode::Normal || self.mode == ViewMode::Raw => @@ -330,11 +342,13 @@ impl Component for EnvelopeView { ViewMode::Raw }; self.dirty = true; + return true; } UIEventType::Input(Key::Char('r')) if self.mode.is_attachment() || self.mode == ViewMode::Subview => { self.mode = ViewMode::Normal; self.subview.take(); self.dirty = true; + return true; } UIEventType::Input(Key::Char('a')) if !self.cmd_buf.is_empty() && self.mode == ViewMode::Normal => @@ -362,7 +376,7 @@ impl Component for EnvelopeView { "Multipart attachments are not supported yet.".to_string(), ), }); - return; + return true; } ContentType::Unsupported { .. } => { let attachment_type = u.mime_type(); @@ -386,7 +400,7 @@ impl Component for EnvelopeView { attachment_type )), }); - return; + return true; } } } @@ -398,10 +412,11 @@ impl Component for EnvelopeView { lidx )), }); - return; + return true; } }; - } + return true; + }, UIEventType::Input(Key::Char('g')) if !self.cmd_buf.is_empty() && self.mode == ViewMode::Url => { @@ -422,7 +437,7 @@ impl Component for EnvelopeView { lidx )), }); - return; + return true; } }; @@ -432,6 +447,7 @@ impl Component for EnvelopeView { .stdout(Stdio::piped()) .spawn() .expect("Failed to start xdg_open"); + return true; } UIEventType::Input(Key::Char('u')) => { match self.mode { @@ -440,14 +456,11 @@ impl Component for EnvelopeView { _ => {} } self.dirty = true; + return true; } _ => {} } - if let Some(ref mut sub) = self.subview { - sub.process_event(event, context); - } else if let Some(ref mut p) = self.pager { - p.process_event(event, context); - } + false } fn is_dirty(&self) -> bool { self.dirty diff --git a/ui/src/components/mail/view/html.rs b/ui/src/components/mail/view/html.rs index 1fdc5875e..bb308a0fe 100644 --- a/ui/src/components/mail/view/html.rs +++ b/ui/src/components/mail/view/html.rs @@ -23,6 +23,7 @@ use super::*; use std::io::Write; use std::process::{Command, Stdio}; +#[derive(Debug)] pub struct HtmlView { pager: Pager, bytes: Vec, @@ -65,7 +66,10 @@ impl Component for HtmlView { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { self.pager.draw(grid, area, context); } - fn process_event(&mut self, event: &UIEvent, context: &mut Context) { + fn process_event(&mut self, event: &UIEvent, context: &mut Context) -> bool { + if self.pager.process_event(event, context) { + return true; + } match event.event_type { UIEventType::Input(Key::Char('v')) => { // TODO: Optional filter that removes outgoing resource requests (images and @@ -88,11 +92,11 @@ impl Component for HtmlView { )), }); } - return; + return true; } _ => {} } - self.pager.process_event(event, context); + false } fn is_dirty(&self) -> bool { self.pager.is_dirty() diff --git a/ui/src/components/mail/view/mod.rs b/ui/src/components/mail/view/mod.rs index ccbfe7ce2..e830bd026 100644 --- a/ui/src/components/mail/view/mod.rs +++ b/ui/src/components/mail/view/mod.rs @@ -42,6 +42,12 @@ enum ViewMode { Subview, } +impl Default for ViewMode { + fn default() -> Self { + ViewMode::Normal + } +} + impl ViewMode { fn is_attachment(&self) -> bool { match self { @@ -53,9 +59,9 @@ impl ViewMode { /// Contains an Envelope view, with sticky headers, a pager for the body, and subviews for more /// menus +#[derive(Debug, Default)] pub struct MailView { coordinates: (usize, usize, usize), - local_collection: Vec, pager: Option, subview: Option>, dirty: bool, @@ -74,13 +80,11 @@ impl fmt::Display for MailView { impl MailView { pub fn new( coordinates: (usize, usize, usize), - local_collection: Vec, pager: Option, subview: Option>, ) -> Self { MailView { coordinates, - local_collection, pager, subview, dirty: true, @@ -207,24 +211,17 @@ impl Component for MailView { let upper_left = upper_left!(area); let bottom_right = bottom_right!(area); - let (envelope_idx, y): (usize, usize) = { + let y: usize = { let accounts = &mut context.accounts; - let threaded = accounts[self.coordinates.0].runtime_settings.conf().threaded(); let mailbox = &mut accounts[self.coordinates.0][self.coordinates.1] .as_ref() .unwrap(); - let envelope_idx: usize = if threaded { - mailbox.threaded_mail(self.coordinates.2) - } else { - self.local_collection[self.coordinates.2] - }; - - let envelope: &Envelope = &mailbox.collection[envelope_idx]; + let envelope: &Envelope = &mailbox.collection[self.coordinates.2]; if self.mode == ViewMode::Raw { clear_area(grid, area); context.dirty_areas.push_back(area); - (envelope_idx, get_y(upper_left) - 1) + get_y(upper_left) - 1 } else { let (x, y) = write_string_to_grid( &format!("Date: {}", envelope.date_as_str()), @@ -295,7 +292,7 @@ impl Component for MailView { context .dirty_areas .push_back((upper_left, set_y(bottom_right, y + 1))); - (envelope_idx, y + 1) + y + 1 } }; @@ -304,7 +301,7 @@ impl Component for MailView { let mailbox = &context.accounts[mailbox_idx.0][mailbox_idx.1] .as_ref() .unwrap(); - let envelope: &Envelope = &mailbox.collection[envelope_idx]; + let envelope: &Envelope = &mailbox.collection[mailbox_idx.2]; let op = context.accounts[mailbox_idx.0].backend.operation(envelope.hash()); let body = envelope.body(op); match self.mode { @@ -341,7 +338,16 @@ impl Component for MailView { } } - fn process_event(&mut self, event: &UIEvent, context: &mut Context) { + fn process_event(&mut self, event: &UIEvent, context: &mut Context) -> bool { + if let Some(ref mut sub) = self.subview { + if sub.process_event(event, context) { + return true; + } + } else if let Some(ref mut p) = self.pager { + if p.process_event(event, context) { + return true; + } + } match event.event_type { UIEventType::Input(Key::Esc) => { self.cmd_buf.clear(); @@ -372,17 +378,11 @@ impl Component for MailView { { let accounts = &context.accounts; - let threaded = accounts[self.coordinates.0].runtime_settings.conf().threaded(); let mailbox = &accounts[self.coordinates.0][self.coordinates.1] .as_ref() .unwrap(); - let envelope_idx: usize = if threaded { - mailbox.threaded_mail(self.coordinates.2) - } else { - self.local_collection[self.coordinates.2] - }; - let envelope: &Envelope = &mailbox.collection[envelope_idx]; + let envelope: &Envelope = &mailbox.collection[self.coordinates.2]; let op = context.accounts[self.coordinates.0].backend.operation(envelope.hash()); if let Some(u) = envelope.body(op).attachments().get(lidx) { match u.content_type() { @@ -391,7 +391,7 @@ impl Component for MailView { match EnvelopeWrapper::new(u.bytes().to_vec()) { Ok(wrapper) => { self.subview = Some(Box::new(EnvelopeView::new(wrapper, None, None))); - }, + }, Err(e) => { context.replies.push_back(UIEvent { id: 0, @@ -401,7 +401,7 @@ impl Component for MailView { }); } } - return; + return true; }, ContentType::Text { .. } => { @@ -415,7 +415,7 @@ impl Component for MailView { "Multipart attachments are not supported yet.".to_string(), ), }); - return; + return true; } ContentType::Unsupported { .. } => { let attachment_type = u.mime_type(); @@ -439,7 +439,7 @@ impl Component for MailView { attachment_type )), }); - return; + return true; } } } @@ -451,7 +451,7 @@ impl Component for MailView { lidx )), }); - return; + return true; } }; } @@ -462,17 +462,11 @@ impl Component for MailView { self.cmd_buf.clear(); let url = { let accounts = &context.accounts; - let threaded = accounts[self.coordinates.0].runtime_settings.conf().threaded(); let mailbox = &accounts[self.coordinates.0][self.coordinates.1] .as_ref() .unwrap(); - let envelope_idx: usize = if threaded { - mailbox.threaded_mail(self.coordinates.2) - } else { - self.local_collection[self.coordinates.2] - }; - let envelope: &Envelope = &mailbox.collection[envelope_idx]; + let envelope: &Envelope = &mailbox.collection[self.coordinates.2]; let finder = LinkFinder::new(); let op = context.accounts[self.coordinates.0].backend.operation(envelope.hash()); let mut t = envelope.body(op).text().to_string(); @@ -487,7 +481,7 @@ impl Component for MailView { lidx )), }); - return; + return true; } }; @@ -506,13 +500,9 @@ impl Component for MailView { } self.dirty = true; } - _ => {} - } - if let Some(ref mut sub) = self.subview { - sub.process_event(event, context); - } else if let Some(ref mut p) = self.pager { - p.process_event(event, context); + _ => { return false; } } + true } fn is_dirty(&self) -> bool { self.dirty diff --git a/ui/src/components/mail/view/thread.rs b/ui/src/components/mail/view/thread.rs index eedc10389..e00c07244 100644 --- a/ui/src/components/mail/view/thread.rs +++ b/ui/src/components/mail/view/thread.rs @@ -20,6 +20,7 @@ */ use super::*; +use std::cmp; #[derive(Debug)] struct ThreadEntry { @@ -28,30 +29,113 @@ struct ThreadEntry { content: CellBuffer, msg_idx: usize, - expanded: bool, } -#[derive(Debug)] +#[derive(Debug, Default)] pub struct ThreadView { + new_cursor_pos: usize, + cursor_pos: usize, + expanded_pos: usize, + new_expanded_pos: usize, dirty: bool, coordinates: (usize, usize, usize), - pager: Pager, + mailview: MailView, entries: Vec, + content: CellBuffer, + initiated: bool, } impl ThreadView { - pub fn new(coordinates: (usize, usize, usize), - ) -> Self { - ThreadView { - dirty: true, - coordinates, - pager: Pager::default(), - entries: Vec::new(), + pub fn new(coordinates: (usize, usize, usize), context: &Context) -> Self { + let mut stack: Vec<(usize, usize)> = Vec::with_capacity(32); + let mailbox = &context.accounts[coordinates.0][coordinates.1].as_ref().unwrap(); + let threads = &mailbox.threads; + let container = &threads.containers()[threads.root_set()[coordinates.2]]; + + if container.message().is_some() { + stack.push((0, threads.root_set()[coordinates.2])); + } else { + stack.push((1, container.first_child().unwrap())); } + let mut view = ThreadView { + dirty: true, + initiated: false, + coordinates, + mailview: MailView::default(), + entries: Vec::new(), + cursor_pos: 1, + new_cursor_pos: 0, + .. Default::default() + }; + let mut line = 0; + let mut max_ind = 0; + while let Some((ind, idx)) = stack.pop() { + max_ind = cmp::max(max_ind, ind); + let entry = view.make_entry(context, (ind, idx, line)); + view.entries.push(entry); + line += 1; + let container = &threads.containers()[idx]; + if let Some(i) = container.next_sibling() { + stack.push((ind, i)); + } + + if let Some(i) = container.first_child() { + stack.push((ind + 1, i)); + } + } + view.new_expanded_pos = view.entries.len() - 1; + view.expanded_pos = view.new_expanded_pos + 1; + + let height = 2*view.entries.len(); + let mut width = 0; + + let mut strings: Vec = Vec::with_capacity(view.entries.len()); + + + for e in &view.entries { + let envelope: &Envelope = &mailbox.collection[e.msg_idx]; + strings.push(format!(" {}{} - {}", " ".repeat(e.index.0 * 4), envelope.date_as_str(), envelope.from_to_string())); + width = cmp::max(width, e.index.0 + strings.last().as_ref().unwrap().len() + 1); + + } + let mut content = CellBuffer::new(width, height, Cell::default()); + for (y, e) in view.entries.iter().enumerate() { + if y > 0 && content.get_mut(e.index.0 * 4, 2*y - 1).is_some() { + let index = (e.index.0 * 4, 2*y - 1); + if content[index].ch() == ' ' { + let mut ctr = 1; + while content[(e.index.0 * 4 + ctr, 2 * y - 1)].ch() == ' ' { + content[(e.index.0 * 4 + ctr, 2 * y - 1)].set_ch(HORZ_BOUNDARY); + ctr += 1; + } + content[index].set_ch(_TOP_LEFT_CORNER); + } else { + content[index].set_ch(LIGHT_DOWN_AND_HORIZONTAL); + }; + } + + write_string_to_grid( + &strings[y], + &mut content, + Color::Default, + Color::Default, + ((e.index.0 + 1, 2*y), (width - 1, height - 1)), + true, + ); + content[(e.index.0 * 4, 2*y)].set_ch(VERT_BOUNDARY); + for i in (e.index.0 * 4)..width { + content[(i, 2*y + 1)].set_ch(HORZ_BOUNDARY); + } + content[(e.index.0 *4, 2*y + 1)].set_ch(LIGHT_UP_AND_HORIZONTAL); + } + //view.mailview = MailView::new((view.coordinates.0, view.coordinates.1, view.entries[view.expanded_pos].msg_idx), None, None); + view.content = content; + view.new_cursor_pos = view.new_expanded_pos; + view } - fn make_entry(&mut self, context: &mut Context, i: (usize, usize, usize)) -> ThreadEntry { + fn make_entry(&mut self, context: &Context, i: (usize, usize, usize)) -> ThreadEntry { let (ind, idx, order) = i; let mailbox = &context.accounts[self.coordinates.0][self.coordinates.1] .as_ref() @@ -90,7 +174,7 @@ impl ThreadView { &mut buf, Color::Byte(33), Color::Default, - ((0, 0), (width, height)), + ((ind, 0), (width, height)), true, ); write_string_to_grid( @@ -98,7 +182,7 @@ impl ThreadView { &mut buf, Color::Byte(33), Color::Default, - ((0, 1), (width, height)), + ((ind, 1), (width, height)), true, ); write_string_to_grid( @@ -106,7 +190,7 @@ impl ThreadView { &mut buf, Color::Byte(33), Color::Default, - ((0, 2), (width, height)), + ((ind, 2), (width, height)), true, ); @@ -116,9 +200,71 @@ impl ThreadView { content: buf, msg_idx, - expanded: false, } } + + fn highlight_line(&self, grid: &mut CellBuffer, area: Area, idx: usize) { + let fg_color = Color::Default; + let bg_color = if self.cursor_pos == idx { + Color::Byte(246) + } else { + Color::Default + }; + change_colors(grid, area, fg_color, bg_color); + } + + /// Draw the list + fn draw_list(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { + let upper_left = upper_left!(area); + let bottom_right = bottom_right!(area); + let (width, height) = self.content.size(); + if height == 0 { + clear_area(grid, area); + copy_area(grid, &self.content, area, ((0, 0), (width - 1, 0))); + context.dirty_areas.push_back(area); + return; + } + let rows = (get_y(bottom_right) - get_y(upper_left) + 1) / 2; + let prev_page_no = (self.cursor_pos).wrapping_div(rows); + let page_no = (self.new_cursor_pos).wrapping_div(rows); + + let top_idx = page_no * rows; + + /* If cursor position has changed, remove the highlight from the previous position and + * apply it in the new one. */ + if prev_page_no == page_no { + let old_cursor_pos = self.cursor_pos; + self.cursor_pos = self.new_cursor_pos; + for &idx in &[old_cursor_pos, self.new_cursor_pos] { + let new_area = ( + set_y(upper_left, get_y(upper_left) + 2*(idx % rows)), + set_y(bottom_right, get_y(upper_left) + 2*(idx % rows)), + ); + self.highlight_line(grid, new_area, idx); + context.dirty_areas.push_back(new_area); + } + return; + } else if self.cursor_pos != self.new_cursor_pos { + self.cursor_pos = self.new_cursor_pos; + } + + /* Page_no has changed, so draw new page */ + copy_area( + grid, + &self.content, + area, + ((0, 2*top_idx), (width - 1, height - 1)), + ); + self.highlight_line( + grid, + ( + set_y(upper_left, get_y(upper_left) + 2*(self.cursor_pos % rows)), + set_y(bottom_right, get_y(upper_left) + 2*(self.cursor_pos % rows)), + ), + self.cursor_pos, + ); + context.dirty_areas.push_back(area); + } } impl fmt::Display for ThreadView { @@ -132,11 +278,27 @@ impl Component for ThreadView { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { let upper_left = upper_left!(area); let bottom_right = bottom_right!(area); - clear_area(grid, area); - let mut stack: Vec<(usize, usize)> = Vec::with_capacity(32); - let y = - { + let total_rows = get_y(bottom_right) - get_y(upper_left); + let pager_ratio = context.runtime_settings.pager.pager_ratio; + let bottom_entity_rows = (pager_ratio * total_rows) / 100; + + if bottom_entity_rows > total_rows { + clear_area(grid, area); + context.dirty_areas.push_back(area); + return; + } + + let mid = get_y(upper_left) + total_rows - bottom_entity_rows; + + if !self.dirty { + self.mailview.draw(grid, (set_y(upper_left, mid + 1), bottom_right), context); + return; + } + + self.dirty = false; + + let y = { let mailbox = &mut context.accounts[self.coordinates.0][self.coordinates.1].as_ref().unwrap(); let threads = &mailbox.threads; let container = &threads.containers()[threads.root_set()[self.coordinates.2]]; @@ -162,62 +324,80 @@ impl Component for ThreadView { grid[(x, y)].set_bg(Color::Default); grid[(x, y)].set_fg(Color::Default); } - if container.message().is_some() { - stack.push((0, threads.root_set()[self.coordinates.2])); - } else { - stack.push((1, container.first_child().unwrap())); - } - y + //context.dirty_areas.push_back(((0,0), set_y(bottom_right, y))); + y + 2 }; - if self.dirty { - - let mut line = 0; - while let Some((ind, idx)) = stack.pop() { - let entry = self.make_entry(context, (ind, idx, line)); - self.entries.push(entry); - line += 1; - let mailbox = &mut context.accounts[self.coordinates.0][self.coordinates.1].as_ref().unwrap(); - let threads = &mailbox.threads; - let container = &threads.containers()[idx]; - if let Some(i) = container.next_sibling() { - stack.push((ind, i)); - } - if let Some(i) = container.first_child() { - stack.push((ind + 1, i)); - } - } - let height = self.entries.iter().map(|x| x.content.size().1).sum(); - let width = self.entries.iter().map(|x| x.content.size().0).max().unwrap_or(0); - let mut content = CellBuffer::new(width, height, Cell::default()); - - let mut y = 0; - - let (cols_dest, rows_dest) = content.size(); - for e in &self.entries { - let (cols_src, rows_src) = e.content.size(); - y = copy_area(&mut content, &e.content, ((0, y), (cols_dest - 1, rows_dest - 1)), ((0, 0), (cols_src - 1, rows_src - 1))).1; - for i in 0..cols_dest { - content[(i, y)].set_ch(HORZ_BOUNDARY); - } - } - self.pager = Pager::from_buf(content, None); - self.dirty = false; + let (width, height) = self.content.size(); + if height == 0 || height == self.cursor_pos || width == 0 { + return; } - self.pager.draw(grid, (set_y(upper_left, y + 1), bottom_right), context); + if !self.initiated { + clear_area(grid, (set_y(upper_left, y - 1), bottom_right)); + let (width, height) = self.content.size(); + copy_area( + grid, + &self.content, + (set_y(upper_left, y), set_y(bottom_right, mid - 1)), + ((0, 0), (width - 1, height - 1)), + ); + for x in get_x(upper_left)..=get_x(bottom_right) { + grid[(x, mid)].set_ch(HORZ_BOUNDARY); + } + if let Some(cell) = grid.get_mut(get_x(upper_left).saturating_sub(1), mid) { + if cell.ch() == VERT_BOUNDARY { + cell.set_ch(LIGHT_VERTICAL_AND_RIGHT); + } + } + context.dirty_areas.push_back(area); + self.initiated = true; + } - context - .dirty_areas - .push_back(area); + if self.new_expanded_pos != self.expanded_pos { + self.expanded_pos = self.new_expanded_pos; + self.mailview = MailView::new((self.coordinates.0, self.coordinates.1, self.entries[self.expanded_pos].msg_idx), None, None); + } + self.draw_list(grid, (set_y(upper_left, y), set_y(bottom_right, mid - 1)), context); + self.mailview.draw(grid, (set_y(upper_left, mid + 1), bottom_right), context); } - fn process_event(&mut self, event: &UIEvent, context: &mut Context) { - self.pager.process_event(event, context); + fn process_event(&mut self, event: &UIEvent, context: &mut Context) -> bool { + if self.mailview.process_event(event, context) { + return true; + } + match event.event_type { + UIEventType::Input(Key::Up) => { + if self.cursor_pos > 0 { + self.new_cursor_pos -= 1; + self.dirty = true; + } + return true; + }, + UIEventType::Input(Key::Down) => { + let height = self.entries.len(); + if height > 0 && self.cursor_pos + 1 < height { + self.new_cursor_pos += 1; + self.dirty = true; + } + return true; + } + UIEventType::Input(Key::Char('\n')) => { + self.new_expanded_pos = self.cursor_pos; + self.dirty = true; + return true; + }, + UIEventType::Resize => { + self.dirty = true; + }, + _ => {} + } + false } fn is_dirty(&self) -> bool { - self.dirty || self.pager.is_dirty() + self.dirty || self.mailview.is_dirty() } fn set_dirty(&mut self) { self.dirty = true; + self.mailview.set_dirty(); } } diff --git a/ui/src/components/mod.rs b/ui/src/components/mod.rs index 6a5839a89..d4ecb6d50 100644 --- a/ui/src/components/mod.rs +++ b/ui/src/components/mod.rs @@ -36,7 +36,7 @@ pub mod utilities; pub use self::utilities::*; use std::fmt; -use std::fmt::Display; +use std::fmt::{Display, Debug}; use std::ops::Deref; use super::{Key, UIEvent, UIEventType}; @@ -64,11 +64,19 @@ const LIGHT_UP_AND_HORIZONTAL: char = '┴'; /// `Entity` is a container for Components. Totally useless now so if it is not useful in the /// future (ie hold some information, id or state) it should be removed. +#[derive(Debug)] pub struct Entity { //context: VecDeque, pub component: Box, // more than one? } +impl Display for Entity { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + Display::fmt(&self.component, f) + } +} + + impl Deref for Entity { type Target = Box; @@ -79,17 +87,17 @@ impl Deref for Entity { impl Entity { /// Pass events to child component. - pub fn rcv_event(&mut self, event: &UIEvent, context: &mut Context) { - self.component.process_event(&event, context); + pub fn rcv_event(&mut self, event: &UIEvent, context: &mut Context) -> bool { + self.component.process_event(&event, context) } } /// Types implementing this Trait can draw on the terminal and receive events. /// If a type wants to skip drawing if it has not changed anything, it can hold some flag in its /// fields (eg self.dirty = false) and act upon that in their `draw` implementation. -pub trait Component: Display { +pub trait Component: Display + Debug { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context); - fn process_event(&mut self, event: &UIEvent, context: &mut Context); + fn process_event(&mut self, event: &UIEvent, context: &mut Context) -> bool; fn is_dirty(&self) -> bool { true } @@ -228,7 +236,7 @@ fn write_string_to_grid( if x == (get_x(bottom_right)) + 1 || x > get_x(bounds) { x = get_x(upper_left); y += 1; - if y >= (get_y(bottom_right)) || y > get_y(bounds) { + if y > (get_y(bottom_right)) || y > get_y(bounds) { return (x, y - 1); } if !line_break { diff --git a/ui/src/components/notifications.rs b/ui/src/components/notifications.rs index d659b8f01..dfab70bb4 100644 --- a/ui/src/components/notifications.rs +++ b/ui/src/components/notifications.rs @@ -27,6 +27,7 @@ use notify_rust::Notification as notify_Notification; use super::*; /// Passes notifications to the OS using the XDG specifications. +#[derive(Debug)] pub struct XDGNotifications {} impl fmt::Display for XDGNotifications { @@ -38,7 +39,7 @@ impl fmt::Display for XDGNotifications { impl Component for XDGNotifications { fn draw(&mut self, _grid: &mut CellBuffer, _area: Area, _context: &mut Context) {} - fn process_event(&mut self, event: &UIEvent, _context: &mut Context) { + fn process_event(&mut self, event: &UIEvent, _context: &mut Context) -> bool { if let UIEventType::Notification(ref t) = event.event_type { notify_Notification::new() .summary("Refresh Event") @@ -47,6 +48,7 @@ impl Component for XDGNotifications { .show() .unwrap(); } + false } fn set_dirty(&mut self) {} } diff --git a/ui/src/components/utilities.rs b/ui/src/components/utilities.rs index 7aad252a0..de9bd0d6b 100644 --- a/ui/src/components/utilities.rs +++ b/ui/src/components/utilities.rs @@ -24,6 +24,7 @@ use super::*; /// A horizontally split in half container. +#[derive(Debug)] pub struct HSplit { top: Entity, bottom: Entity, @@ -34,7 +35,7 @@ pub struct HSplit { impl fmt::Display for HSplit { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // TODO display subject/info - self.top.fmt(f) + Display::fmt(&self.top, f) } } @@ -81,9 +82,8 @@ impl Component for HSplit { context, ); } - fn process_event(&mut self, event: &UIEvent, context: &mut Context) { - self.top.rcv_event(event, context); - self.bottom.rcv_event(event, context); + fn process_event(&mut self, event: &UIEvent, context: &mut Context) -> bool { + self.top.rcv_event(event, context) || self.bottom.rcv_event(event, context) } fn is_dirty(&self) -> bool { self.top.component.is_dirty() || self.bottom.component.is_dirty() @@ -95,6 +95,7 @@ impl Component for HSplit { } /// A vertically split in half container. +#[derive(Debug)] pub struct VSplit { left: Entity, right: Entity, @@ -106,7 +107,7 @@ pub struct VSplit { impl fmt::Display for VSplit { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // TODO display focused entity - self.right.fmt(f) + Display::fmt(&self.right, f) } } @@ -169,9 +170,8 @@ impl Component for VSplit { .component .draw(grid, ((mid + 1, get_y(upper_left)), bottom_right), context); } - fn process_event(&mut self, event: &UIEvent, context: &mut Context) { - self.left.rcv_event(event, context); - self.right.rcv_event(event, context); + fn process_event(&mut self, event: &UIEvent, context: &mut Context) -> bool { + (self.left.rcv_event(event, context) || self.right.rcv_event(event, context)) } fn is_dirty(&self) -> bool { self.left.component.is_dirty() || self.right.component.is_dirty() @@ -350,7 +350,7 @@ impl Component for Pager { if self.height == 0 || self.height == self.cursor_pos || self.width == 0 { return; } - + clear_area(grid, area); //let pager_context: usize = context.settings.pager.pager_context; //let pager_stop: bool = context.settings.pager.pager_stop; @@ -364,7 +364,7 @@ impl Component for Pager { ); context.dirty_areas.push_back(area); } - fn process_event(&mut self, event: &UIEvent, _context: &mut Context) { + fn process_event(&mut self, event: &UIEvent, _context: &mut Context) -> bool { match event.event_type { UIEventType::Input(Key::Char('k')) => { if self.cursor_pos > 0 { @@ -388,12 +388,15 @@ impl Component for Pager { } UIEventType::ChangeMode(UIMode::Normal) => { self.dirty = true; + return false; } UIEventType::Resize => { self.dirty = true; + return false; } - _ => {} + _ => { return false; } } + true } fn is_dirty(&self) -> bool { self.dirty @@ -404,6 +407,7 @@ impl Component for Pager { } /// Status bar. +#[derive(Debug)] pub struct StatusBar { container: Entity, status: String, @@ -513,14 +517,17 @@ impl Component for StatusBar { _ => {} } } - fn process_event(&mut self, event: &UIEvent, context: &mut Context) { - self.container.rcv_event(event, context); + fn process_event(&mut self, event: &UIEvent, context: &mut Context) -> bool { + if self.container.rcv_event(event, context) { + return true; + } + match &event.event_type { UIEventType::RefreshMailbox((ref idx_a, ref idx_f)) => { match context.accounts[*idx_a].status(*idx_f) { Ok(_) => {} Err(_) => { - return; + return false; } } let m = &context.accounts[*idx_a][*idx_f].as_ref().unwrap(); @@ -558,14 +565,17 @@ impl Component for StatusBar { UIEventType::ExInput(Key::Char(c)) => { self.dirty = true; self.ex_buffer.push(*c); + return true; } UIEventType::ExInput(Key::Ctrl('u')) => { self.dirty = true; self.ex_buffer.clear(); + return true; } UIEventType::ExInput(Key::Backspace) | UIEventType::ExInput(Key::Ctrl('h')) => { self.dirty = true; self.ex_buffer.pop(); + return true; } UIEventType::Resize => { self.dirty = true; @@ -576,6 +586,7 @@ impl Component for StatusBar { } _ => {} } + return false; } fn is_dirty(&self) -> bool { self.dirty || self.container.component.is_dirty() @@ -586,6 +597,7 @@ impl Component for StatusBar { } // A box with a text content. +#[derive(Debug)] pub struct TextBox { _content: String, } @@ -605,10 +617,11 @@ impl fmt::Display for TextBox { impl Component for TextBox { fn draw(&mut self, _grid: &mut CellBuffer, _area: Area, _context: &mut Context) {} - fn process_event(&mut self, _event: &UIEvent, _context: &mut Context) {} + fn process_event(&mut self, _event: &UIEvent, _context: &mut Context) -> bool { false } fn set_dirty(&mut self) {} } +#[derive(Debug)] pub struct Progress { description: String, total_work: usize, @@ -655,12 +668,13 @@ impl Component for Progress { fn draw(&mut self, _grid: &mut CellBuffer, _area: Area, _context: &mut Context) { unimplemented!() } - fn process_event(&mut self, _event: &UIEvent, _context: &mut Context) { - return; + fn process_event(&mut self, _event: &UIEvent, _context: &mut Context) -> bool { + return false; } fn set_dirty(&mut self) {} } +#[derive(Debug)] pub struct Tabbed { children: Vec>, cursor_pos: usize, @@ -736,16 +750,16 @@ impl Component for Tabbed { self.children[self.cursor_pos].draw(grid, area, context); } } - fn process_event(&mut self, event: &UIEvent, context: &mut Context) { + fn process_event(&mut self, event: &UIEvent, context: &mut Context) -> bool { match &event.event_type { UIEventType::Input(Key::Char('T')) => { self.cursor_pos = (self.cursor_pos + 1) % self.children.len(); self.children[self.cursor_pos].set_dirty(); - return; + return true; } _ => {} } - self.children[self.cursor_pos].process_event(event, context); + self.children[self.cursor_pos].process_event(event, context) } fn is_dirty(&self) -> bool { self.children[self.cursor_pos].is_dirty() diff --git a/ui/src/types/accounts.rs b/ui/src/types/accounts.rs index e2724ca78..855a2dce9 100644 --- a/ui/src/types/accounts.rs +++ b/ui/src/types/accounts.rs @@ -94,11 +94,9 @@ impl Account { pub fn list_folders(&self) -> Vec { let mut folders = self.backend.folders(); if let Some(folder_renames) = self.settings.conf().folders() { - eprintln!("folder renames: {:?}", folder_renames); + //eprintln!("folder renames: {:?}", folder_renames); for f in &mut folders { - eprintln!("f.name() is {}", f.name()); if let Some(name) = folder_renames.get(&f.name().to_ascii_lowercase()) { - eprintln!("name is {}", name); f.change_name(name); } } @@ -211,7 +209,7 @@ impl Account { return Err(n); } a => { - eprintln!("{:?}", a); + eprintln!("Error: {:?}", a); return Err(0); } }, diff --git a/ui/src/types/cells.rs b/ui/src/types/cells.rs index 596fb2b11..7a1e96fef 100644 --- a/ui/src/types/cells.rs +++ b/ui/src/types/cells.rs @@ -111,6 +111,9 @@ impl fmt::Debug for CellBuffer { } impl CellBuffer { + pub fn area(&self) -> Area { + ((0, 0), (self.cols.saturating_sub(1), self.rows.saturating_sub(1))) + } pub fn set_cols(&mut self, new_cols: usize) { self.cols = new_cols; }