1use itertools::Itertools;
2use ratatui::{
3 buffer::Buffer,
4 layout::{Constraint, Layout, Margin, Rect},
5 style::{Styled, Stylize},
6 text::Line,
7 widgets::{
8 Block, BorderType, Borders, Clear, List, ListItem, ListState, Padding, Paragraph,
9 Scrollbar, ScrollbarState, StatefulWidget, Tabs, Widget,
10 },
11};
12use unicode_width::UnicodeWidthStr;
13
14use crate::{RgbSwatch, THEME};
15
16#[derive(Debug, Default)]
17pub struct Email {
18 from: &'static str,
19 subject: &'static str,
20 body: &'static str,
21}
22
23const EMAILS: &[Email] = &[
24 Email {
25 from: "Alice <alice@example.com>",
26 subject: "Hello",
27 body: "Hi Bob,\nHow are you?\n\nAlice",
28 },
29 Email {
30 from: "Bob <bob@example.com>",
31 subject: "Re: Hello",
32 body: "Hi Alice,\nI'm fine, thanks!\n\nBob",
33 },
34 Email {
35 from: "Charlie <charlie@example.com>",
36 subject: "Re: Hello",
37 body: "Hi Alice,\nI'm fine, thanks!\n\nCharlie",
38 },
39 Email {
40 from: "Dave <dave@example.com>",
41 subject: "Re: Hello (STOP REPLYING TO ALL)",
42 body: "Hi Everyone,\nPlease stop replying to all.\n\nDave",
43 },
44 Email {
45 from: "Eve <eve@example.com>",
46 subject: "Re: Hello (STOP REPLYING TO ALL)",
47 body: "Hi Everyone,\nI'm reading all your emails.\n\nEve",
48 },
49];
50
51#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
52pub struct EmailTab {
53 row_index: usize,
54}
55
56impl EmailTab {
57 pub fn prev(&mut self) {
59 self.row_index = self.row_index.saturating_add(EMAILS.len() - 1) % EMAILS.len();
60 }
61
62 pub fn next(&mut self) {
64 self.row_index = self.row_index.saturating_add(1) % EMAILS.len();
65 }
66}
67
68impl Widget for EmailTab {
69 fn render(self, area: Rect, buf: &mut Buffer) {
70 RgbSwatch.render(area, buf);
71 let area = area.inner(Margin {
72 vertical: 1,
73 horizontal: 2,
74 });
75 Clear.render(area, buf);
76 let vertical = Layout::vertical([Constraint::Length(5), Constraint::Min(0)]);
77 let [inbox, email] = vertical.areas(area);
78 render_inbox(self.row_index, inbox, buf);
79 render_email(self.row_index, email, buf);
80 }
81}
82fn render_inbox(selected_index: usize, area: Rect, buf: &mut Buffer) {
83 let vertical = Layout::vertical([Constraint::Length(1), Constraint::Min(0)]);
84 let [tabs, inbox] = vertical.areas(area);
85 let theme = THEME.email;
86 Tabs::new(vec![" Inbox ", " Sent ", " Drafts "])
87 .style(theme.tabs)
88 .highlight_style(theme.tabs_selected)
89 .select(0)
90 .divider("")
91 .render(tabs, buf);
92
93 let highlight_symbol = ">>";
94 let from_width = EMAILS
95 .iter()
96 .map(|e| e.from.width())
97 .max()
98 .unwrap_or_default();
99 let items = EMAILS.iter().map(|e| {
100 let from = format!("{:width$}", e.from, width = from_width).into();
101 ListItem::new(Line::from(vec![from, " ".into(), e.subject.into()]))
102 });
103 let mut state = ListState::default().with_selected(Some(selected_index));
104 StatefulWidget::render(
105 List::new(items)
106 .style(theme.inbox)
107 .highlight_style(theme.selected_item)
108 .highlight_symbol(highlight_symbol),
109 inbox,
110 buf,
111 &mut state,
112 );
113 let mut scrollbar_state = ScrollbarState::default()
114 .content_length(EMAILS.len())
115 .position(selected_index);
116 Scrollbar::default()
117 .begin_symbol(None)
118 .end_symbol(None)
119 .track_symbol(None)
120 .thumb_symbol("▐")
121 .render(inbox, buf, &mut scrollbar_state);
122}
123
124fn render_email(selected_index: usize, area: Rect, buf: &mut Buffer) {
125 let theme = THEME.email;
126 let email = EMAILS.get(selected_index);
127 let block = Block::new()
128 .style(theme.body)
129 .padding(Padding::new(2, 2, 0, 0))
130 .borders(Borders::TOP)
131 .border_type(BorderType::Thick);
132 let inner = block.inner(area);
133 block.render(area, buf);
134 if let Some(email) = email {
135 let vertical = Layout::vertical([Constraint::Length(3), Constraint::Min(0)]);
136 let [headers_area, body_area] = vertical.areas(inner);
137 let headers = vec![
138 Line::from(vec![
139 "From: ".set_style(theme.header),
140 email.from.set_style(theme.header_value),
141 ]),
142 Line::from(vec![
143 "Subject: ".set_style(theme.header),
144 email.subject.set_style(theme.header_value),
145 ]),
146 "-".repeat(inner.width as usize).dim().into(),
147 ];
148 Paragraph::new(headers)
149 .style(theme.body)
150 .render(headers_area, buf);
151 let body = email.body.lines().map(Line::from).collect_vec();
152 Paragraph::new(body)
153 .style(theme.body)
154 .render(body_area, buf);
155 } else {
156 Paragraph::new("No email selected").render(inner, buf);
157 }
158}