Skip to main content

mxr_tui/app/
draw.rs

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