1use super::*;
2
3fn tab_titles() -> [&'static str; 5] {
4 [
5 "1 Mailbox",
6 "2 Search",
7 "3 Rules",
8 "4 Accounts",
9 "5 Diagnostics",
10 ]
11}
12
13fn selected_tab(screen: Screen) -> usize {
14 match screen {
15 Screen::Mailbox => 0,
16 Screen::Search => 1,
17 Screen::Rules => 2,
18 Screen::Accounts => 3,
19 Screen::Diagnostics => 4,
20 }
21}
22
23impl App {
24 pub fn draw(&mut self, frame: &mut Frame) {
25 let theme = &self.theme;
26 let area = frame.area();
27
28 let outer_chunks = Layout::default()
30 .direction(Direction::Vertical)
31 .constraints([
32 Constraint::Length(1), Constraint::Length(2), Constraint::Min(0), Constraint::Length(1), ])
37 .split(area);
38
39 let tab_bar_area = outer_chunks[0];
40 let hint_bar_area = outer_chunks[1];
41 let content_area = outer_chunks[2];
42 self.visible_height = content_area.height.saturating_sub(2) as usize;
44 let bottom_bar_area = outer_chunks[3];
45
46 let tabs = ratatui::widgets::Tabs::new(tab_titles())
48 .select(selected_tab(self.screen))
49 .style(Style::default().fg(theme.text_muted))
50 .highlight_style(Style::default().fg(theme.accent).bold())
51 .divider(Span::styled(" | ", Style::default().fg(theme.text_muted)));
52 frame.render_widget(tabs, tab_bar_area);
53
54 ui::hint_bar::draw(
56 frame,
57 hint_bar_area,
58 ui::hint_bar::HintBarState {
59 screen: self.screen,
60 active_pane: &self.active_pane,
61 search_active: self.search_bar.active,
62 help_modal_open: self.help_modal_open,
63 selected_count: self.selected_set.len(),
64 bulk_confirm_open: self.pending_bulk_confirm.is_some(),
65 sync_status: self.last_sync_status.clone(),
66 },
67 theme,
68 );
69
70 match self.screen {
71 Screen::Mailbox => match self.layout_mode {
72 LayoutMode::TwoPane => {
73 let chunks = Layout::default()
74 .direction(Direction::Horizontal)
75 .constraints([Constraint::Percentage(20), Constraint::Percentage(80)])
76 .split(content_area);
77
78 ui::sidebar::draw(
79 frame,
80 chunks[0],
81 &ui::sidebar::SidebarView {
82 labels: &self.labels,
83 active_pane: &self.active_pane,
84 saved_searches: &self.saved_searches,
85 sidebar_selected: self.sidebar_selected,
86 all_mail_active: !self.search_active
87 && self.mailbox_view == MailboxView::Messages
88 && self.active_label.is_none()
89 && self.pending_active_label.is_none(),
90 subscriptions_active: self.mailbox_view == MailboxView::Subscriptions,
91 subscription_count: self.subscriptions_page.entries.len(),
92 system_expanded: self.sidebar_system_expanded,
93 user_expanded: self.sidebar_user_expanded,
94 saved_searches_expanded: self.sidebar_saved_searches_expanded,
95 active_label: self
96 .pending_active_label
97 .as_ref()
98 .or(self.active_label.as_ref()),
99 },
100 theme,
101 );
102
103 if self.mailbox_view == MailboxView::Subscriptions {
104 ui::subscriptions_page::draw(
105 frame,
106 chunks[1],
107 &ui::subscriptions_page::SubscriptionsPageView {
108 entries: &self.subscriptions_page.entries,
109 selected_index: self.selected_index,
110 scroll_offset: self.scroll_offset,
111 active_pane: &self.active_pane,
112 preview_blocks: &self.thread_message_blocks(),
113 message_scroll_offset: self.message_scroll_offset,
114 },
115 theme,
116 );
117 } else {
118 let mail_title = self.mail_list_title();
119 ui::mail_list::draw_view(
120 frame,
121 chunks[1],
122 &ui::mail_list::MailListView {
123 rows: &self.mail_list_rows(),
124 selected_index: self.selected_index,
125 scroll_offset: self.scroll_offset,
126 active_pane: &self.active_pane,
127 title: &mail_title,
128 selected_set: &self.selected_set,
129 mode: self.mail_list_mode,
130 },
131 theme,
132 );
133 }
134 }
135 LayoutMode::ThreePane => {
136 let chunks = Layout::default()
137 .direction(Direction::Horizontal)
138 .constraints([Constraint::Percentage(15), Constraint::Percentage(85)])
139 .split(content_area);
140
141 ui::sidebar::draw(
142 frame,
143 chunks[0],
144 &ui::sidebar::SidebarView {
145 labels: &self.labels,
146 active_pane: &self.active_pane,
147 saved_searches: &self.saved_searches,
148 sidebar_selected: self.sidebar_selected,
149 all_mail_active: !self.search_active
150 && self.mailbox_view == MailboxView::Messages
151 && self.active_label.is_none()
152 && self.pending_active_label.is_none(),
153 subscriptions_active: self.mailbox_view == MailboxView::Subscriptions,
154 subscription_count: self.subscriptions_page.entries.len(),
155 system_expanded: self.sidebar_system_expanded,
156 user_expanded: self.sidebar_user_expanded,
157 saved_searches_expanded: self.sidebar_saved_searches_expanded,
158 active_label: self
159 .pending_active_label
160 .as_ref()
161 .or(self.active_label.as_ref()),
162 },
163 theme,
164 );
165
166 if self.mailbox_view == MailboxView::Subscriptions {
167 ui::subscriptions_page::draw(
168 frame,
169 chunks[1],
170 &ui::subscriptions_page::SubscriptionsPageView {
171 entries: &self.subscriptions_page.entries,
172 selected_index: self.selected_index,
173 scroll_offset: self.scroll_offset,
174 active_pane: &self.active_pane,
175 preview_blocks: &self.thread_message_blocks(),
176 message_scroll_offset: self.message_scroll_offset,
177 },
178 theme,
179 );
180 } else {
181 let inner = Layout::default()
182 .direction(Direction::Horizontal)
183 .constraints([Constraint::Percentage(41), Constraint::Percentage(59)])
184 .split(chunks[1]);
185 let mail_title = self.mail_list_title();
186 ui::mail_list::draw_view(
187 frame,
188 inner[0],
189 &ui::mail_list::MailListView {
190 rows: &self.mail_list_rows(),
191 selected_index: self.selected_index,
192 scroll_offset: self.scroll_offset,
193 active_pane: &self.active_pane,
194 title: &mail_title,
195 selected_set: &self.selected_set,
196 mode: self.mail_list_mode,
197 },
198 theme,
199 );
200 ui::message_view::draw(
201 frame,
202 inner[1],
203 &self.thread_message_blocks(),
204 self.message_scroll_offset,
205 &self.active_pane,
206 theme,
207 );
208 }
209 }
210 LayoutMode::FullScreen => {
211 if self.mailbox_view == MailboxView::Subscriptions {
212 ui::subscriptions_page::draw(
213 frame,
214 content_area,
215 &ui::subscriptions_page::SubscriptionsPageView {
216 entries: &self.subscriptions_page.entries,
217 selected_index: self.selected_index,
218 scroll_offset: self.scroll_offset,
219 active_pane: &self.active_pane,
220 preview_blocks: &self.thread_message_blocks(),
221 message_scroll_offset: self.message_scroll_offset,
222 },
223 theme,
224 );
225 } else {
226 ui::message_view::draw(
227 frame,
228 content_area,
229 &self.thread_message_blocks(),
230 self.message_scroll_offset,
231 &self.active_pane,
232 theme,
233 );
234 }
235 }
236 },
237 Screen::Search => {
238 let rows = self.search_mail_list_rows();
239 ui::search_page::draw(
240 frame,
241 content_area,
242 &self.search_page,
243 &rows,
244 self.mail_list_mode,
245 &self.thread_message_blocks(),
246 self.message_scroll_offset,
247 theme,
248 );
249 }
250 Screen::Rules => {
251 ui::rules_page::draw(frame, content_area, &self.rules_page, theme);
252 }
253 Screen::Diagnostics => {
254 ui::diagnostics_page::draw(frame, content_area, &self.diagnostics_page, theme);
255 }
256 Screen::Accounts => {
257 ui::accounts_page::draw(frame, content_area, &self.accounts_page, theme);
258 }
259 }
260
261 let status_bar = self.status_bar_state();
262 ui::status_bar::draw(frame, bottom_bar_area, &status_bar, theme);
263
264 if self.search_bar.active {
265 ui::search_bar::draw(frame, area, &self.search_bar, theme);
266 }
267
268 ui::command_palette::draw(frame, area, &self.command_palette, theme);
270
271 ui::label_picker::draw(frame, area, &self.label_picker, theme);
273
274 ui::compose_picker::draw(frame, area, &self.compose_picker, theme);
276
277 ui::attachment_modal::draw(frame, area, &self.attachment_panel, theme);
279
280 ui::url_modal::draw(frame, area, self.url_modal.as_ref(), theme);
282
283 ui::snooze_modal::draw(frame, area, &self.snooze_panel, &self.snooze_config, theme);
285
286 ui::send_confirm_modal::draw(frame, area, self.pending_send_confirm.as_ref(), theme);
288
289 ui::bulk_confirm_modal::draw(frame, area, self.pending_bulk_confirm.as_ref(), theme);
291
292 ui::error_modal::draw(frame, area, self.error_modal.as_ref(), theme);
294
295 ui::unsubscribe_modal::draw(
297 frame,
298 area,
299 self.pending_unsubscribe_confirm.as_ref(),
300 theme,
301 );
302
303 ui::help_modal::draw(
305 frame,
306 area,
307 ui::help_modal::HelpModalState {
308 open: self.help_modal_open,
309 screen: self.screen,
310 active_pane: &self.active_pane,
311 selected_count: self.selected_set.len(),
312 scroll_offset: self.help_scroll_offset,
313 },
314 theme,
315 );
316 }
317}
318
319#[cfg(test)]
320mod tests {
321 use super::tab_titles;
322
323 #[test]
324 fn tab_titles_include_numeric_shortcuts() {
325 assert_eq!(
326 tab_titles(),
327 [
328 "1 Mailbox",
329 "2 Search",
330 "3 Rules",
331 "4 Accounts",
332 "5 Diagnostics",
333 ]
334 );
335 }
336}