From 3de317af353df6e5c0c11b25ab41c80540c9b6c1 Mon Sep 17 00:00:00 2001 From: FliegendeWurst <2012gdwu+github@posteo.de> Date: Sat, 15 May 2021 19:32:59 +0200 Subject: [PATCH] browse: custom mail part view --- src/bin/browse.rs | 161 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 136 insertions(+), 25 deletions(-) diff --git a/src/bin/browse.rs b/src/bin/browse.rs index 16cbff3..fd426be 100644 --- a/src/bin/browse.rs +++ b/src/bin/browse.rs @@ -1,8 +1,9 @@ #![feature(internal_output_capture)] use std::{cell::RefCell, cmp, collections::{HashMap, HashSet}, env, fmt::Display, io, sync::{Arc, atomic::{AtomicBool, Ordering}}}; +use std::result::Result as StdResult; -use cursive::{Cursive, Vec2, WrapMethod, view::ViewWrapper}; +use cursive::{Cursive, Vec2, WrapMethod, traits::Finder, view::ViewWrapper}; use cursive::align::HAlign; use cursive::event::{Event, Key}; use cursive::traits::Identifiable; @@ -13,7 +14,7 @@ use inboxid::*; use io::Write; use itertools::Itertools; use log::error; -use mailparse::{MailHeaderMap, ParsedMail}; +use mailparse::{MailHeaderMap, ParsedMail, parse_mail}; use parking_lot::{Mutex, RwLock}; use petgraph::{EdgeDirection, graph::{DiGraph, NodeIndex}, visit::{Dfs, IntoNodeReferences}}; use rusqlite::params; @@ -214,15 +215,8 @@ fn show_listing(mailbox: &str) -> Result<()> { siv.call_on_name("mail_info", |view: &mut MailInfoView| { view.set(item); }); - siv.call_on_name("mail", |view: &mut TextView| { - let body = if mail.ctype.mimetype == "text/html" { - let html = mail.get_body().unwrap(); - html2text::from_read(html.as_bytes(), 120) - // TODO: this logic is duplicated below + the actual width should be considered - } else { - mail.get_body().unwrap() - }; - view.set_content(body); + siv.call_on_name("mail", |view: &mut MailPartView| { + view.set_part(mail); }); siv.call_on_name("mail_scroller", |view: &mut MailScrollerView| { view.scroll_to_top(); @@ -285,32 +279,33 @@ fn show_listing(mailbox: &str) -> Result<()> { }); let tree_resized = ResizedView::new(SizeConstraint::AtMost(120), SizeConstraint::Free, tree); let mail_info = MailInfoView::new().with_name("mail_info"); - let mail_content = TextView::new("").with_name("mail"); + let mail_content = MailPartView::empty().with_name("mail"); static MAIL_FULLSCREEN: AtomicBool = AtomicBool::new(false); - let dummy = std::rc::Rc::new(RefCell::new(Some(OnEventView::new(TextView::new("dummy").with_name("dummy")).scrollable()))); // TODO dummy content + let dummy = std::rc::Rc::new(RefCell::new(Some(OnEventView::new(MailView::empty().with_name("dummy")).scrollable()))); // TODO dummy content let dummy_ = dummy.clone(); let mail_content = OnEventView::new(mail_content) .on_event('f', move |s| { let dummy__ = dummy_.clone(); if MAIL_FULLSCREEN.load(Ordering::SeqCst) { let layer = s.pop_layer().unwrap(); - if let Ok(textview) = layer.downcast::>>>>() { + if let Ok(textview) = layer.downcast::>() { let mut it = textview.into_inner().unwrap_or_else(|_| panic!("?")); it.set_show_scrollbars(true); it.get_inner_mut().get_inner_mut().get_mut().set_wrap_method(WrapMethod::XiUnicode); dummy__.borrow_mut().replace(it); - s.call_on_name("mail_scroller", move |this: &mut ScrollView>>| { + s.call_on_name("mail_scroller", move |this: &mut MailScrollerView| { std::mem::swap(dummy__.borrow_mut().as_mut().unwrap(), this); }); } MAIL_FULLSCREEN.store(false, Ordering::SeqCst); } else { - s.call_on_name("mail_scroller", move |this: &mut ScrollView>>| { + s.call_on_name("mail_scroller", move |this: &mut MailScrollerView| { std::mem::swap(dummy__.borrow_mut().as_mut().unwrap(), this); }); let mut it = dummy_.borrow_mut().take().unwrap(); it.set_show_scrollbars(false); it.get_inner_mut().get_inner_mut().get_mut().set_wrap_method(WrapMethod::Newlines); + eprintln!("adding fullscreen layer!"); s.add_fullscreen_layer(ResizedView::with_full_screen(it)); MAIL_FULLSCREEN.store(true, Ordering::SeqCst); } @@ -322,14 +317,8 @@ fn show_listing(mailbox: &str) -> Result<()> { let mail = siv.call_on_name("part_select", |tree: &mut TreeView| { tree.borrow_item(row).unwrap().part }).unwrap(); - siv.call_on_name("mail", |view: &mut TextView| { - let body = if mail.ctype.mimetype == "text/html" { - let html = mail.get_body().unwrap(); - html2text::from_read(html.as_bytes(), 120) - } else { - mail.get_body().unwrap() - }; - view.set_content(body); + siv.call_on_name("mail", |view: &mut MailView| { + view.set_part(mail); }); }); mail_part_select.set_on_submit(|siv, _row| { @@ -411,7 +400,7 @@ fn show_listing(mailbox: &str) -> Result<()> { } type MailScrollerView = ScrollView>>; -type MailView = TextView; +type MailView = MailPartView; type MailTreeView<'a> = TreeView<&'a EasyMail<'a>>; #[derive(Debug)] @@ -435,6 +424,128 @@ impl From<&'static ParsedMail<'static>> for MailPart { impl TreeEntry for MailPart {} +struct MailPartView { + part: Option<&'static ParsedMail<'static>>, + wrap: WrapMethod, + text: Option, + cached_size: Option +} + +impl MailPartView { + fn empty() -> Self { + MailPartView { + part: None, + wrap: WrapMethod::XiUnicode, + text: None, + cached_size: None + } + } + + fn set_wrap_method(&mut self, wrap: WrapMethod) { + if let Some(text) = self.text.as_mut() { + text.set_wrap_method(wrap); + } + self.wrap = wrap; + } + + fn set_part(&mut self, part: &'static ParsedMail<'static>) { + self.part = Some(part); + self.text = None; + self.cached_size = None; + } + + fn setup_text(&mut self, size: Vec2) { + if self.part.is_none() { + return; + } + let part = self.part.unwrap(); + let body = if part.ctype.mimetype == "text/html" { + let html = part.get_body().unwrap(); + eprintln!("HTML layout using {} width", size.x); + html2text::from_read(html.as_bytes(), size.x) + } else { + part.get_body().unwrap() + }; + let mut text = TextView::new(body); + text.set_wrap_method(self.wrap); + self.text = Some(text); + } +} + +impl View for MailPartView { + fn draw(&self, printer: &cursive::Printer) { + eprintln!("drawing with {:?}", printer.output_size); + if let Some(text) = self.text.as_ref() { + text.draw(printer) + } else { + printer.print((0, 0), "missing text cache"); + } + } + + fn layout(&mut self, given_size: Vec2) { + eprintln!("layout called with {:?}", given_size); + if self.cached_size.is_some() && self.cached_size != Some(given_size) { + self.setup_text(given_size); + } + self.cached_size = Some(given_size); + if let Some(text) = self.text.as_mut() { + text.layout(given_size); + } else if self.part.is_some() { + self.setup_text(given_size); + self.text.as_mut().unwrap().layout(given_size); + } + } + + fn needs_relayout(&self) -> bool { + true + } + + fn required_size(&mut self, constraint: Vec2) -> Vec2 { + // just use all of the available space + if let Some(text) = self.text.as_mut() { + let text_constraint = text.required_size(constraint); + let (x, y) = text_constraint.pair(); + (x.max(constraint.x), y.max(constraint.y)).into() + } else { + constraint + } + } + + fn on_event(&mut self, ev: Event) -> cursive::event::EventResult { + if let Some(text) = self.text.as_mut() { + text.on_event(ev) + } else { + cursive::event::EventResult::Ignored + } + } + + fn call_on_any<'a>(&mut self, sel: &cursive::view::Selector<'_>, cb: cursive::event::AnyCb<'a>) { + if let Some(text) = self.text.as_mut() { + text.call_on_any(sel, cb) + } + } + + fn focus_view(&mut self, sel: &cursive::view::Selector<'_>) -> StdResult<(), cursive::view::ViewNotFound> { + if let Some(text) = self.text.as_mut() { + text.focus_view(sel) + } else { + Err(cursive::view::ViewNotFound) + } + } + + fn take_focus(&mut self, _source: cursive::direction::Direction) -> bool { + true + } + + fn important_area(&self, view_size: Vec2) -> cursive::Rect { + if let Some(text) = self.text.as_ref() { + text.important_area(view_size) + } else { + cursive::Rect::from_size((0, 30), view_size) + } + } +} + struct MailInfoView { email: Option<&'static ParsedMail<'static>> }